ant-design 1.x版本表格头部拖拽、可拖拽列实现
表格列宽拖拽调整 — 问题总结版本“vue”: “2.6.11”,“vue-draggable-resizable”: “^2.3.0”,ant-design “”1.7.0“问题 1thDom为 null 导致getBoundingClientRect报错现象TypeError: Cannot read properties of null (reading getBoundingClientRect)根因onDrag中this.$set(draggingState, key, 0)触发 Vue 响应式重渲染。旧th销毁时 Vue 2 的ref回调用null清理闭包中的thDom之后dragstop事件拿到的就是null。解决用非响应式对象this._dragX作为拖拽手柄x的数据源onDrag期间不触发重渲染仅在onDragstop时通过$set提交最终值。外加if (!thDom) return防御。问题 2vue-draggable-resizabletransform 不对、拖拽错列、拖拽点乱跑现象拖拽 handle 的transform位置计算错误拖拽 A 列实际改变了 B 列的宽度。根因vue-draggable-resizable内部用transform: translateX()驱动拖拽但 CSS 把 handle 固定在right: -5pxleft: auto !important。组件内部的x定位和 CSS 定位坐标系冲突onDrag上报的x值不可信。解决移除vue-draggable-resizable依赖改用原生mousedown/mousemove/mouseup实现。Handle 仅靠 CSSright: -5px定位用鼠标位移增量delta计算新宽度彻底消除坐标系冲突。问题 3拖拽一次后位置不确定刷新 / 跳动现象拖拽过程中 handle 位置跳动释放后列宽不确定。根因同问题 1onDrag触发重渲染导致vue-draggable-resizable的xprop 被重置。解决随问题 2 一并解决原生事件方案不依赖任何响应式状态驱动拖拽位置。问题 4Scoped 样式不生效 — handlewidth: 0导致无法展示拖拽按钮现象.table-draggable-handle的width: 10px不生效handle 宽度为 0无法拖拽。其他样式如position: absolute正常。根因Vue 2 scoped 样式通过data-v-xxx属性匹配但h()在 render 函数中创建的元素不在 template 里不会自动添加 scope 属性。scoped 块line 814的width对 handle 不生效实际走的是非 scoped 块line 1040而该块之前缺少width。解决在非 scoped 样式块中补充width: 10px; top: 0; z-index: 1去掉vue-draggable-resizable时代的残留样式height: 100% !important; left: auto !important。问题 5替换 header cell 后排序失效现象拖拽列无法点击排序。根因ant-design-vue 传给components.header.cell的props是完整 VNode data 对象含class、on、style等顶层属性但代码把所有属性塞进了attrs{ attrs: { ...restProps } }导致on.click排序回调、class排序样式丢失。解决改为正确分层// 之前错误{attrs:{...restProps,width:col.width},class:resize-table-th}// 之后正确{...restProps,attrs:{...restProps.attrs,width:col.width},class:[resize-table-th,restProps.class]}问题 6拖拽结束后触发了排序现象拖拽过程中sorter图标变化释放鼠标后触发了排序请求。根因mousedown之后会触发click事件click冒泡到th被 ant-design-vue 的排序逻辑捕获。解决在拖拽 handle 上添加click: e { e.stopPropagation() }阻止click事件冒泡到th。问题 7depColumns树形结构只遍历了顶层现象depColumns是树形结构{ title: 基本情况, children: [...] }叶子节点才有dataIndex和width。但created初始化和resizeableTitle查找都只处理了顶层数组。解决两处都改为递归遍历created—walkColumns递归展开所有层级只收集叶子节点无children或children为空的列resizeableTitle—findColumn递归搜索children找到匹配的列问题 8需要按列控制可拖拽 最大/最小宽度需求只有标记了isDraggable: true的列才出现拖拽手柄支持minWidth/maxWidth限制拖拽范围解决判断条件从!col.width改为!col.isDraggableonMouseMove中用Math.min(Math.max(newWidth, col.minWidth || 50), col.maxWidth || Infinity)限制范围minWidth 默认 50px最终列配置示例{title:业务线,dataIndex:serviceLine,width:70,isDraggable:true,// 显式开启拖拽minWidth:100,// 最小宽度默认 50maxWidth:400,// 最大宽度默认无上限}最终列代码示例templatea-table bordered:columnscolumns:componentstableComponents:data-sourcedata/a-table/templatescriptimportVuefromvue;constcolumns[{title:Date,dataIndex:date,width:200,},{title:Amount,dataIndex:amount,width:100,},{title:Type,dataIndex:type,width:100,},{title:Note,dataIndex:note,width:100,},{title:Action,key:action,isDraggable:true,// 显式开启拖拽minWidth:100,// 最小宽度默认 50maxWidth:400,// 最大宽度默认无上限},];constdata[{key:0,date:2018-02-11,amount:120,type:income,note:transfer,},{key:1,date:2018-03-11,amount:243,type:income,note:transfer,},{key:2,date:2018-04-11,amount:98,type:income,note:transfer,},];exportdefault{name:App,data(){return{depColumns:data,};},computed:{tableComponents(){return{header:{cell:this.resizeableTitle,},}},},created(){constdraggingMap{}constwalkColumns(cols){cols.forEach(col{if(col.childrencol.children.length){walkColumns(col.children)}else{constkcol.dataIndex||col.keyif(col.width){draggingMap[k]col.width}}})}walkColumns(this.depColumns)this.draggingStatedraggingMap},methods:{resizeableTitle(h,props,children){letthDomnullconst{key,...restProps}propsconstfindColumn(cols,key){for(constcolofcols){if(col.childrencol.children.length){constfoundfindColumn(col.children,key)if(found)returnfound}else{constkcol.dataIndex||col.keyif(kkey)returncol}}returnnull}constcolfindColumn(this.depColumns,key)if(!col||!col.isDraggable){returnh(th,{...restProps},children)}letstartX0letstartWidth0constonMouseDowne{e.preventDefault()e.stopPropagation()startXe.pageX startWidththDom?thDom.getBoundingClientRect().width:col.widthconstonMouseMovee{constdeltae.pageX-startXconstnewWidthstartWidthdeltaconstmincol.minWidth||50constmaxcol.maxWidth||Infinitycol.widthMath.min(Math.max(newWidth,min),max)}constonMouseUp(){document.removeEventListener(mousemove,onMouseMove)document.removeEventListener(mouseup,onMouseUp)document.body.style.cursordocument.body.style.userSelectconstfinalWidththDom?thDom.getBoundingClientRect().width:col.widththis.$set(this.draggingState,key,finalWidth)col.widthfinalWidth}document.addEventListener(mousemove,onMouseMove)document.addEventListener(mouseup,onMouseUp)document.body.style.cursorcol-resizedocument.body.style.userSelectnone}consthandleElh(div,{class:table-draggable-handle,on:{mousedown:onMouseDown,click:e{e.stopPropagation()},},})returnh(th,{...restProps,attrs:{...restProps.attrs,width:col.width},class:[resize-table-th,restProps.class],ref:r{thDomr},},[...(Array.isArray(children)?children:[children]),handleEl,])},}};/scriptstyle langless.resize-table-th{position:relative;.table-draggable-handle{position:absolute;top:0;right:-5px;bottom:0;width:10px;z-index:1;cursor:col-resize;touch-action:none;}}/style