我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

vxe-table单选取消选中:allowCancel配置与实战指南

vxe-table单选取消选中:allowCancel配置与实战指南 前阵子在做一个后台系统的列表页产品提了个需求列表支持单选但用户点错了之后能再点一下取消选中。这个需求听起来很简单但放到 vxe-table 上默认行为却完全不是这样单选列一旦选中再点一下并不会取消。很多前端朋友第一次遇到都会愣住。实际上 vxe-table 专门提供了radio-config配置其中allowCancel就是用来打开取消选中的开关。这篇文章我就把完整的配置方式、事件处理和我实际踩过的坑一起整理出来给正在用 vxe-table 做单选场景的同学一个参考。1. 默认单选为何一根筋先搞清楚 vxe-table 的交互哲学1.1 单选列的最简写法在 vxe-table 里做单选一般不需要自己维护选中状态直接加一列typeradio就够了。代码长这样template vxe-table :datatableData vxe-column typeradio width60/vxe-column vxe-column fieldname title姓名/vxe-column vxe-column fieldage title年龄/vxe-column /vxe-table /template script export default { data() { return { tableData: [ { id: 1, name: 张三, age: 28 }, { id: 2, name: 李四, age: 32 }, { id: 3, name: 王五, age: 25 } ] } } } /script这样跑起来每一行前面会多出一个单选圆圈点击任意一行就能选中。注意这里没有写radio-change事件也没有绑定任何数据因为 vxe-table 自己会把选中行记下来。你只需要在合适的时机通过表格实例去取。但问题也来了当你再次点击已经选中的那一行预料中的取消选中并没有发生那个圆点还是实心的。第一次用 vxe-table 做这个需求的同学十有八九会去查文档有没有clearRadio有没有setRadioRow这些方法确实存在但都不是针对交互层取消的它们属于编程式控制。1.2 为什么默认不允许取消是合理的很多组件库比如 el-table也存在类似逻辑单选通常被设计成必须有一个值因为它语义上对应着表单里的 radio 控件。radio 控件本身一旦进入某个选项组就天然要有一个选中项取消选中并不是 radio 的原生行为。vxe-table 沿用这个思路把不允许取消作为单选列的默认值是为了保证数据一致性只要列表处于单选模式就始终有一个选中行后端和逻辑层处理起来都简单。但真实业务往往不是表单语义而是列表选择操作。例如用户在列表里选择一个订单去执行操作后来又想清空选择重新选或者这个列表本身是可选项预览选中和未选中都要被允许。这种场景下必须选一个反而成了束缚于是才需要allowCancel来打破默认行为。对我来说理解这个默认值背后的意图很重要。如果不知道这点你可能会在radio-change事件里做一些很别扭的处理比如发现newValue一直不为空误以为事件没触发。2. 打开取消开关radio-config 里的 allowCancel 和配套项2.1 allowCancel 的准确摆放位置要允许取消只需要在vxe-table上绑定一个radio-config对象把allowCancel设为true。注意这个配置是表格级的不是写在vxe-column上的。很多新手容易把radio-config放到单选列上结果怎么配都没反应。正确写法vxe-table :datatableData :radio-config{ trigger: row, allowCancel: true } vxe-column typeradio width60/vxe-column vxe-column fieldname title姓名/vxe-column vxe-column fieldage title年龄/vxe-column /vxe-tabletrigger: row表示整行点击都触发单选操作这是 vxe-table 的默认触发方式也是后台列表最常见的交互。配置完成后再刷新页面点击任意一行会选中再点一次同一行圆点会变成空心选中状态被取消。整个交互不需要写一行 JS 逻辑。2.2 trigger 的另一个选择只允许点击单元格触发如果觉得整行触发太灵敏尤其是行内还有编辑删除这类按钮时可以把trigger改成cell。此时只有点击放了typeradio的那个单元格也就是行首的单选圆圈区域才会触发选中或取消点击行内其他位置不会影响单选状态。vxe-table :datatableData :radio-config{ trigger: cell, allowCancel: true } vxe-column typeradio width60/vxe-column !-- 其他列 -- /vxe-table这里有个细节容易被忽略trigger: cell并不是说只能点击单选圆圈本身而是点击该列的任意单元格区域都会触发。也就是说你在列头之外给这列设置了很宽的宽度点击单元格空白处也能切换。这通常没问题但如果你的业务要求只有点中圆圈才响应那还需要配合自定义单元格模板来做一般场景用不到这里不再展开。2.3 参数小结与版本说明下面是我的项目里实际用到的一组radio-config常用参数参数类型默认值说明triggerStringrow单选触发方式可选 row 或 cellallowCancelBooleanfalse是否允许取消选中highlightBooleanfalse选中行是否高亮配合样式更直观checkMethodFunction-动态控制某行是否可以被选中allowCancel是这里最关键的参数。至于版本我当前用的 vxe-table 4.x 版本已经完全支持这些配置如果你是 2.x 或更早的版本建议升级到 3.0 以上。升级后如果发现allowCancel不生效除了检查版本还要检查是不是把配置放错了层级。另外提醒一下highlight参数不影响取消但建议打开。关闭高亮后用户取消选中时看不出状态变化很容易以为按钮坏了。3. 取消不只是点击事件捕获、动态开关和编程式清空3.1 通过 radio-change 判断用户是否取消了选中配置好allowCancel之后我们还需要知道用户到底选了哪一行或者取消了哪一行。vxe-table 提供radio-change事件在选中状态变化时触发。事件参数里有row、oldValue、newValue三个关键字段。具体规则可以这么理解用户从未选中任何行然后点击第 2 行row是第 2 行oldValue是nullnewValue是第 2 行。用户当前选中第 1 行然后点击第 3 行row是第 3 行oldValue是第 1 行newValue是第 3 行。用户当前选中第 1 行然后再次点击第 1 行且允许取消row是第 1 行oldValue是第 1 行newValue是null。所以在事件回调里判断取消选中最简单的方式就是看newValue是否为nullvxe-table :datatableData :radio-config{ trigger: row, allowCancel: true } radio-changehandleRadioChange !-- 列定义 -- /vxe-tablehandleRadioChange({ row, oldValue, newValue }) { if (!newValue) { // 取消选中了 console.log(取消选中的行, oldValue) return } // 选中了某一行 console.log(当前选中行, newValue) }这里有个容易混淆的地方取消时row和oldValue指向的是同一行newValue为null。如果你只关注row会以为用户选中了这行因为row有值。我第一次就踩过这个坑在回调里拿到row就直接提交结果取消时也提交了一行数据。后来改成判断newValue才恢复正常。3.2 动态控制允许取消开关有些场景下是否允许取消不是一成不变的。比如列表有一个可编辑开关打开时才允许用户取消选中关闭时必须保证有一个选中值。这时直接把radio-config做成动态对象就行template vxe-table refxTable :datatableData :radio-configradioConfig radio-changehandleRadioChange vxe-column typeradio width60/vxe-column vxe-column fieldname title姓名/vxe-column /vxe-table /template script export default { data() { return { canCancel: false, tableData: [] } }, computed: { radioConfig() { return { trigger: row, allowCancel: this.canCancel } } }, methods: { toggleCancel() { this.canCancel !this.canCancel }, handleRadioChange({ row, oldValue, newValue }) { // ... } } } /script需要注意的是动态修改radioConfig会立即对后续交互生效但它不会自动清除当前已经选中的行。如果你从允许取消切回不允许取消而此刻列表正处于无选中状态那么页面会保持无选中直到用户重新点击某一行。如果业务上要求切回时必须强制选中某行你需要在切换逻辑里调用一次表格的编程式方法。3.3 编程式清空和设置选中行除了用户手动取消很多时候还需要在代码里主动取消或设置选中。比如点击重置按钮清空选择。vxe-table 的实例方法里有两个常用的clearRadio()和setRadioRow(row)。clearRadio()清空当前所有单选选中状态。setRadioRow(row)指定某一行作为选中行传入的是该行数据对象。用法如下this.$refs.xTable.clearRadio() this.$refs.xTable.setRadioRow(this.tableData[1])这两个方法受radio-config的限制吗基本不受。setRadioRow可以设置任何一行但要注意如果你同时配置了checkMethod并且该方法对该行返回false那么setRadioRow可能被拒绝。clearRadio()则不管allowCancel是 true 还是 false 都能清空这也是和allowCancel的区别allowCancel控制的是用户点击交互而编程方法是你主动调用组件不会拦截。在我做的项目里重置按钮就是直接调clearRadio()然后清空后端查询参数。这样写比在数据层去改当前选中行要好得多因为表格内部状态是它自己的外部没必要维护一份可能不同步的副本。4. 完整封装示例一个可取消单选的表格组件4.1 封装思路如果项目里有多个页面都需要单选 可取消的表格每次手写radio-config肯定重复。我把常用逻辑封装成了一个SingleSelectTable组件对外支持v-model父组件只需要传数据选中变化时自动把当前选中行或 null吐出去。组件内部关键代码大致如下template vxe-table refxTable :datatableData :radio-configradioConfig :disableddisabled radio-changehandleRadioChange vxe-column v-ifshowRadioColumn typeradio width60 /vxe-column slot/slot /vxe-table /template script export default { name: SingleSelectTable, model: { prop: selectedRow, event: update:selectedRow }, props: { tableData: { type: Array, default: () [] }, selectedRow: { type: Object, default: null }, allowCancel: { type: Boolean, default: true }, trigger: { type: String, default: row }, showRadioColumn: { type: Boolean, default: true }, disabled: { type: Boolean, default: false } }, data() { return { radioConfig: {} } }, watch: { allowCancel: { immediate: true, handler(val) { this.radioConfig { trigger: this.trigger, allowCancel: val, highlight: true } } } }, methods: { handleRadioChange({ oldValue, newValue }) { this.$emit(update:selectedRow, newValue) if (!newValue) { this.$emit(cancel, oldValue) } }, clear() { this.$refs.xTable.clearRadio() }, setRow(row) { this.$refs.xTable.setRadioRow(row) } } } /script父组件使用single-select-table v-modelselectedRow :table-dataorders allow-cancel cancelhandleCancel vxe-column fieldorderNo title单号/vxe-column vxe-column fieldamount title金额/vxe-column /single-select-table这样封装有几个好处第一所有单选表格用同一套交互规范不会一个页面允许取消、另一个页面不允许第二父组件不用关心表格实例的调用方式v-model直接拿到当前值第三cancel事件让业务方可以独立处理清空动作而不用在radio-change里自己比对newValue是否为空。需要注意封装时不要直接在mounted里用selectedRow调用setRadioRow因为表格数据可能还没渲染完成。如果确实需要回显等tableData赋值后通过nextTick再调用并且确保传入的row是tableData里的同一对象引用否则组件内部可能找不到对应行。4.2 与后端交互时的注意事项可取消单选在后端接口上会引入一个很多人没警觉的问题以前必选的单选接口提交参数是{ selectedId: 123 }现在允许取消了用户清空后你提交什么null还是不做处理如果后端接口没有考虑过 null可能直接报 500。我的习惯是在前端统一定义一个常量或者用undefined表示无选中。提交时做一次转换function buildSubmitParams(selectedRow) { return { selectedId: selectedRow ? selectedRow.id : null } }后端如果用的是 JSON 解析null会被当成空值通常没问题。但有的接口把id定义为数字类型前端传null在某些序列化框架下会被丢弃导致整个参数缺失。所以提交前最好和后端确认好空值协议到底传null、传空字符串还是直接不传这个字段。这个确认最好在开发前就做否则联调阶段很容易互相甩锅。4.3 排查allowCancel 不生效的完整链路就算按照文档配了allowCancel: true也有可能出现点了不取消的情况。我整理了一条排查链路按顺序走一遍基本能定位。第一步确认版本。在控制台打印this.$refs.xTable.$xeTable或者查看 package.json 里vxe-table的版本号。如果低于 3.0先升级。v4 之前和 v4 的配置属性名基本一致但某些早期版本对allowCancel支持不完整。第二步确认配置层级。检查radio-config是写在vxe-table根节点上还是误写到了vxe-column上。后者是无效的且通常不会报错只会让你干瞪眼。第三步确认trigger和点击区域。如果你设置的是trigger: cell但用户点的是行内其他区域当然不会触发取消。把它改成row再试试或者让用户点一下行首单选列。第四步关闭可能的拦截。如果你在行上绑定了click.stop或者在表格外层写了click.prevent会导致 vxe-table 内部的点击事件拿不到 target就算配置再对也没用。可以在浏览器 DevTools 的 Elements 面板里检查点击时有没有触发事件或者临时去掉click.stop验证。第五步查看checkMethod。如果你配置了checkMethod并且返回false那么该行永远不允许被选中也就谈不上取消因为它始终处于未选中状态。这里的逻辑是checkMethod不只是控制用户点击还会影响setRadioRow调用。如果业务上既想让某行不可选中又想让它如果已经被选中时可以取消那checkMethod就不太合适需要换一种控制方式比如在点击事件里手动判断。第六步看事件输出。在radio-change回调里console.log(newValue)如果取消操作真的发生了newValue一定为null。如果回调里打印了newValue但始终有值说明事件触发了但内部认为你并没有执行取消这时优先回头查allowCancel配置。如果回调压根没触发说明点击事件没有命中单选逻辑多半是上面第三步或第四步的问题。这套链路我每次升级表格库后都会重新走一遍因为 v4 版本对配置的校验更严格之前不小心写错的属性会在控制台告警但也有些静默失效的情况还是需要手动排。5. 一点个人使用体会5.1 什么时候不建议用 allowCancel说了这么多配置最后还是要泼一盆冷水allowCancel不是万能的有些场景不建议开。如果这个列表的选中值是整个页面提交的必填项比如结算单列表选择一张单据进行结算那么取消选中会让用户陷入不选就无法下一步的尴尬。这种情况不如保持默认的不取消省掉一层判断也减少用户误操作的可能。但如果列表是筛选条件的一部分或者选完以后还能随时反悔那allowCancel就很有价值。它的语义更接近打勾/取消打勾而不是radio 组必选一项。你可以在界面上加一个已选无的提示让用户清楚当前处于未选择状态。5.2 结合 vxe-grid 的用法如果你用的是vxe-grid带工具栏、分页的完整表格配置方式是一样的只是把radio-config放在vxe-grid标签上。我的项目里大部分列表其实是vxe-grid所以实际生效的代码长这样vxe-grid refxGrid :datatableData :columnscolumns :radio-config{ trigger: row, allowCancel: true } radio-changehandleRadioChange /vxe-grid注意columns数组里同样要有一列配置type: radio。如果使用vxe-grid的columns动态配置记得加 width 列宽否则列会显示得很窄。另外分页场景下默认的radio-change事件返回的是当前页的行对象如果跨页保持单选状态vxe-table 默认不做跨页保留你需要自己在分页变换时通过setRadioRow恢复选中行。不要指望allowCancel能联动分页记忆它只管交互不负责持久化。5.3 最后分享一个排查小技巧如果你的表格数据是异步加载的页面打开后立刻点击行发现选中不了先别急着怀疑allowCancel。这往往是数据还没渲染完成行事件没有正确绑定。可以在setTimeout或者nextTick之后再点击验证。用 vxe-table 的过程里80% 的配置不生效问题其实都不是配置写错而是事件绑定时机和数据加载时机不对。我平时会把所有radio-config相关的配置抽成一个公共常量配合表头按钮清空选择既保证交互统一出现问题时也能快速定位。假如你是第一次接触这个库建议从最小 Demo 开始验证allowCancel跑通后再引入项目。这样至少能确保取消的元凶不是你的业务代码。
返回列表