标题有点长,就如这踩坑的时间,那么就进入这次的踩坑日记。在Vue的Table组件中,实现可伸缩列,如果你使用的是Element-Ui那么这是一个现成的功能,如果你使用的是ant-design-vue,那么是需要集成一个
vue-draggable-resizable
插件的。详细使用这里不用多说,我想大多说开发者是会先把文档中的用法跑一下的,这期的坑就是,这个Demo跑不起来!其中有这样几点肯,我来记录一下。
- Module parse failed: Argument name clash...
- 表格没有拖动滑块
- 拖动时不够流畅且宽度乱跳
- 一些细节处理
下面就来一一解答一下问题。我们先将官方文档的代码拷贝下来
Delete
- 如果你按照上面的文档操作了,那么你第一个一定会遇到
Module parse failed: Argument name clash
其意思是命名发生了冲突,我们有两种方案解决这个问题,
- 将
props
和children
提出来,并给h
变量起一个别名。大概代码如下,当然取值的时候,你也可以使用ES6解构。
const ResizeableTitle = (h1) => {
const props = h1.props
const children = h1.children
- ResizeableTitle 改成 resizeableTitle(我也不知道这个是为啥,但确实解决了)
解决完上面问题后,项目可以正常加载出来了,但是你会发现
- 列并不能拖动,鼠标划上去都没有可以拖动的反应。那么你可以这样修改Css
.resize-table-th {
position: relative;
.table-draggable-handle {
transform: none !important;
position: absolute;
height: 100% !important;
bottom: 0;
left: auto !important;
right: -5px;
cursor: col-resize;
touch-action: none;
}
}
主要有两点.table-draggable-handle 里加上 transform: none !important; position: absolute;去掉style的scoped属性;这时拖动滑块也顺利出来了
- 于是你就试着去滑动。你会发现滑动的并不像官网那样流程,甚至有一些莫名的跳动,大概描述为,向外拖的时候,拖动的点离鼠标距离很远,并且拖动的幅度很大,向内拖动的时候,列宽会忽然变宽一下,然后再变变成幅度很大的拖动,这显然是不符合我们的需求的,我们也要像官网的demo那样流畅。于是我开始读他的代码,大致找出以下毛病
- columns的Item中不一定都是key,(大部分不是key)
columns.forEach(col => {
draggingMap[col.key] = col.width;
});
原代码如上,意思是遍历columns,取其每一项的宽度为值,每一项的key
为键名,生成对象draggingMap
,而实际开发中,columns
的每一项中dataIndex
和key
一般是二选一的,虽然也有共存的情况(没必要也不推荐),但大多数还是二选一,就像原代码中的columns
那样,他定义的都是dataIndex
,而它代码中却取key
,这显然是不严谨的。我们需要把代码改成这样
columns.forEach((col) => {
const k = col.dataIndex || col.key;
draggingMap[k] = col.width;
});
这样才能生成完整的draggingMap
对象,当然,这个对代码功能没有实质性影响,因为后续对draggingMap
的取值中,并不是操作每一个值,而是靠set的方式设置值,所以技术这个对象是一个残缺的,但不影响,我想着也导致作者直接没注意到这里写错了吧。至于何种残缺,自己打断点看,大概就是{undefined: 90, index: 80}
类似这样的对象。接着往下看
- 宽度设置有误
const onDrag = (x) => {
draggingState[key] = 0;
col.width = Math.max(x, 1);
};
const onDragstop = () => {
draggingState[key] = thDom.getBoundingClientRect().width;
};
上面代码的意思是,拖动的时候,所拖动的那一列在draggingState
对象中的值,先置为0,然后将当前的columns
当前操作项的width值设为当前鼠标的位置。拖拽完毕后,再将所拖动的那一列在draggingState
对象中的值,置为当前列的实际宽度。
于是关于col.width
和draggingState[key]
的来历和含义都明白了,其中,我们在初始化的时候,取col.width
生成了draggingState[key]
。这个时候col.width
正是我们在设置columns
时意为设置列宽的值,但是拖动过后,col.width
表示成了拖动时鼠标的终止位置,而这个位置不能再代表宽度了,这时候draggingState[key]
还是columns
中宽度的映射,他始终是没有变化的,所以我们应该将draggingState[key]
当成列宽来使用。所以做下面的改动
{ thDom = r; }}
width={draggingState[key]}
class="resize-table-th"
>
在弄明白两个变量的含义后,关于vue-draggable-resizable
组件中的x属性设置,查看了github
的文档后,发现,这个参数意思为开始拖动时的很初始位置,而这个值正和col.width
的含义类似,他是上次拖动的终止位置,将x设为col.width
,应该就可以保证每次拖动的时候,初始位置是上次拖动的终止位置了。而现在的代码中是
x={draggingState[key] || col.width}
也就是初始位置为列宽,如果没有列宽那就取上次的位置,所以这就导致了每次拖动的时候,列宽都要跳动这样,因为他先取了实际宽度,随后draggingState[key]
变成0,才又取的col.width
,这一块说了半天有点绕哈,反正,他写反了,你要改成x={ col.width || draggingState[key] }
才对。
然后
这段代码也是需要改成这样,同上面的第一个点,你不能保证columns
中每一项都有key,反而大多数是dataIndex
这一问题。
经过上面一番review和分析后,我的表格终于可以像官网那样丝滑了。
下面是我的核心代码,对于某些变量我做了修改,但这一定不会影响聪明的你。我是在混入中写的这些逻辑,你可以自由参照,理性搬运。
const draggingMap = {};
this.columns.forEach((col) => {
const k = col.dataIndex || col.key;
draggingMap[k] = col.width;
});
const draggingState = Vue.observable(draggingMap);
const resizeableTitle = (h, props, children) => {
let thDom = null;
const { key, ...restProps } = props;
const col = this.columns.find((item) => {
const k = item.dataIndex || item.key;
return k === key;
});
if (!col.width) {
return {children} ;
}
const onDrag = (x) => {
draggingState[key] = 0;
col.width = Math.max(x, 1);
};
const onDragstop = () => {
draggingState[key] = thDom.getBoundingClientRect().width;
};
return (
{ thDom = r; }}
width={draggingState[key]}
class="resize-table-th"
>
{children}
);
};
this.components = {
header: {
cell: resizeableTitle,
},
};
},
// less代码如下。
.resize-table-th {
position: relative;
.table-draggable-handle {
transform: none !important;
position: absolute;
height: 100% !important;
bottom: 0;
left: auto !important;
right: -5px;
cursor: col-resize;
touch-action: none;
}
}
上面是我在开发中真实踩到的坑,和真实的分析思路以及上网查的资料。如果有不同意见,请私信我。
补充(加入复选框后报错)
在文章发布后,收到了一些私信,其中有一个问题是,有的同学在表格选择使用复选框后出现问题,这里说明一下,本文章中是对官网demo的修改,官网中 没有封装带复选框的情况,我也就忘了,这里深表歉意。
如果复制上面的代码,加个复选框的情况下,会报下列的错误
Cannot read property 'width' of undefined
这是因为这里这段逻辑导致的
const { key, ...restProps } = props;
const col = this.columns.find((item) => {
const k = item.dataIndex || item.key;
return k === key;
});
这段代码的意思是,我们逐个去遍历每一列,然后拿到当前列的对象,并对其做一些属性的操作,而,我们加了复选框后,在遍历的时候就会有一个key为‘selection-column’的列,导致了col为undefined
,所以在下面的col.width
的逻辑中就报错了。如果要解决此问题,只需要将这个列单独处理即可。
那么你可以这样改造那段逻辑
const { key, ...restProps } = props;
let col;
if (key === 'selection-column') {
col = {};
} else {
col = this.columns.find((item) => {
const k = item.dataIndex || item.key;
return k === key;
});
}
这就OK了~2021年3月4日更新。