当前位置: 首页 > news >正文

ant-design 1.x版本表格头部拖拽、可拖拽列实现

表格列宽拖拽调整 — 问题总结

版本

  • “vue”: “2.6.11”,
  • “vue-draggable-resizable”: “^2.3.0”,
  • "ant-design “:”1.7.0“

问题 1:thDom为 null 导致getBoundingClientRect报错

现象:TypeError: Cannot read properties of null (reading 'getBoundingClientRect')

根因:onDragthis.$set(draggingState, key, 0)触发 Vue 响应式重渲染。旧<th>销毁时 Vue 2 的ref回调用null清理闭包中的thDom,之后dragstop事件拿到的就是null

解决:用非响应式对象this._dragX作为拖拽手柄x的数据源,onDrag期间不触发重渲染,仅在onDragstop时通过$set提交最终值。外加if (!thDom) return防御。

问题 2:vue-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 仅靠 CSS(right: -5px)定位,用鼠标位移增量delta计算新宽度,彻底消除坐标系冲突。

问题 3:拖拽一次后位置不确定刷新 / 跳动

现象:拖拽过程中 handle 位置跳动,释放后列宽不确定。

根因:同问题 1,onDrag触发重渲染导致vue-draggable-resizablexprop 被重置。

解决:随问题 2 一并解决,原生事件方案不依赖任何响应式状态驱动拖拽位置。

问题 4:Scoped 样式不生效 — handlewidth: 0导致无法展示拖拽按钮

现象:.table-draggable-handlewidth: 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.cellprops是完整 VNode data 对象(含classonstyle等顶层属性),但代码把所有属性塞进了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>

问题 7:depColumns树形结构只遍历了顶层

现象:depColumns是树形结构({ title: "基本情况", children: [...] }),叶子节点才有dataIndexwidth。但created初始化和resizeableTitle查找都只处理了顶层数组。

解决:两处都改为递归遍历:

  • createdwalkColumns递归展开所有层级,只收集叶子节点(无childrenchildren为空的列)
  • resizeableTitlefindColumn递归搜索children找到匹配的列

问题 8:需要按列控制可拖拽 + 最大/最小宽度

需求:

  • 只有标记了isDraggable: true的列才出现拖拽手柄
  • 支持minWidth/maxWidth限制拖拽范围

解决:

  • 判断条件从!col.width改为!col.isDraggable
  • onMouseMove中用Math.min(Math.max(newWidth, col.minWidth || 50), col.maxWidth || Infinity)限制范围,minWidth 默认 50px

最终列配置示例

{title:"业务线",dataIndex:"serviceLine",width:70,isDraggable:true,// 显式开启拖拽minWidth:100,// 最小宽度(默认 50)maxWidth:400,// 最大宽度(默认无上限)}

最终列代码示例

<template><a-table bordered:columns="columns":components="tableComponents":data-source="data"></a-table></template><script>importVuefrom'vue';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,// 最小宽度(默认 50)maxWidth: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.children&&col.children.length){walkColumns(col.children)}else{constk=col.dataIndex||col.keyif(col.width){draggingMap[k]=col.width}}})}walkColumns(this.depColumns)this.draggingState=draggingMap},methods:{resizeableTitle(h,props,children){letthDom=nullconst{key,...restProps}=propsconstfindColumn=(cols,key)=>{for(constcolofcols){if(col.children&&col.children.length){constfound=findColumn(col.children,key)if(found)returnfound}else{constk=col.dataIndex||col.keyif(k===key)returncol}}returnnull}constcol=findColumn(this.depColumns,key)if(!col||!col.isDraggable){returnh('th',{...restProps},children)}letstartX=0letstartWidth=0constonMouseDown=e=>{e.preventDefault()e.stopPropagation()startX=e.pageX startWidth=thDom?thDom.getBoundingClientRect().width:col.widthconstonMouseMove=e=>{constdelta=e.pageX-startXconstnewWidth=startWidth+deltaconstmin=col.minWidth||50constmax=col.maxWidth||Infinitycol.width=Math.min(Math.max(newWidth,min),max)}constonMouseUp=()=>{document.removeEventListener('mousemove',onMouseMove)document.removeEventListener('mouseup',onMouseUp)document.body.style.cursor=''document.body.style.userSelect=''constfinalWidth=thDom?thDom.getBoundingClientRect().width:col.widththis.$set(this.draggingState,key,finalWidth)col.width=finalWidth}document.addEventListener('mousemove',onMouseMove)document.addEventListener('mouseup',onMouseUp)document.body.style.cursor='col-resize'document.body.style.userSelect='none'}consthandleEl=h('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=>{thDom=r},},[...(Array.isArray(children)?children:[children]),handleEl,])},}};</script><style lang="less">.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>
http://www.jsqmd.com/news/830085/

相关文章:

  • 2026开发者福音:AgentChat 支持 GPT-Image-2 + Claude 4.7 + OpenClaw 全栈调用,成本降80%!
  • Noto Emoji:告别豆腐块,让表情符号在任何设备上完美显示 [特殊字符]
  • S32K3开发避坑:用EB tresos给GPT定时器(PIT)配时钟,实测24MHz APIS_SLOW_CLK怎么设
  • Python 潮流周刊#150:Python 项目性能分析实践
  • 2026年避坑指南:拨动带灯按键TOP5优选服务商实测推荐 - 速递信息
  • 音频下载工具终极指南:跨平台批量下载解决方案
  • 终极文档下载指南:如何免费一键保存30+平台的在线文档
  • AI 写作进入长篇记忆时代,AI让小说创作更可控
  • 3分钟解锁CAJ文件:如何将知网专属格式转换为可搜索PDF
  • Zynq SoC核心板在电动赛车实时控制系统中的工程实践
  • 别再让PySide6界面卡死了!用QThread实现网络请求的保姆级避坑教程
  • MAA自动化助手深度解析:架构设计与技术实现指南
  • 告别DCOM噩梦:用OPC Expert v8.1.2211一站式搞定OPC连接与监控(附实战配置)
  • 从手忙脚乱到轻松掌控:League Akari如何用3大功能解决英雄联盟玩家的5大痛点
  • 终极指南:如何用MAA明日方舟助手一键解放你的游戏时间
  • 国产LDO CN86L028实战:解决图像传感器电源噪声,兼容BL8062
  • 你的内容被AI“无视”了?这4款GEO管理系统,专治AI引用率低
  • 都是亲生的摄像头,为什么NVR给它们的“回忆”时长不一样?
  • ClawWizard开源工具箱:一站式数据抓取与处理解决方案
  • 终极指南:如何使用ctfileGet告别城通网盘龟速下载
  • 了解CoppeliaSim(原V-REP):灵活的机器人仿真平台及其资源获取指南
  • 3步搞定ModelScope跨平台部署:Windows、Linux、macOS全适配指南
  • 如何快速解锁加密音乐:5种高效方法的完整指南
  • IRISMAN:PS3自定义固件游戏管理工具的技术深度解析
  • GeoServer系列-实战REST接口:从零构建空间数据服务
  • 【实战解析】ST7567G与UC1701E双模LCD屏的SPI驱动与自动识别
  • 终极Cursor Pro破解指南:3种方法实现AI编程助手永久免费使用
  • APKMirror完整指南:如何安全下载历史版本安卓应用
  • 跨境多站点运营自动化实操(附数据同步+效率优化技巧)
  • 保姆级教程:用GATK4分析重测序数据,从fq.gz到vcf文件一步不落