Ant Design of Vue 表格使用 vue-draggable-resizable 封装表头问题汇总

背景

项目需要表格支持拉伸每列的宽度,查看了文档,官方建议是用 vue-draggable-resizable 插件结合 components 属性,给表头 header 增加一个可拖拽的功能。其实下面的前三个问题都很好解决,网上也有很多解决方案,但因为考虑到项目中表格很多,而且每个表格都要支持拉伸列宽度,所以不可能按官方文档那样,每个表格的 vue 组件都写重复的代码,所以就封装了一个方法,专门获取可拖拽的 header 的components。

问题

1、使用 ResizeableTitle 可能会出现命名冲突
报错: Module parse failed: Argument name clash
在这里插入图片描述
解决方式:
把 ResizeableTitle 改成 resizeableTitle 即可,有的时候会出现这个问题,有的时候没有。

2、添加表格选择框后报错
其实主要是官方文档这处代码(如下图)的问题
在这里插入图片描述
那是因为在 antd 的文档中,colums 是没有选择框的,那函数在遍历执行每列时,遇到选择框那一列就找不到对应的 col,col 会为 undefined ,所以必须得对选择框做一个判断处理,可以直接返回 th,不用执行后面的操作。
解决:
在这里插入图片描述
3、表头的一些按钮样式消失,像下图中的滑过样式和点击样式消失,是因为新的th没有继承原来的th的类名。
在这里插入图片描述
如下图官方文档的代码,我们可以看到返回的 th ,要么是没有类名,要么是只有 resize-table-th ,这就很敷衍。
在这里插入图片描述
解决:
其实原有元素的类名,我们可以从函数返回的参数中获取
在这里插入图片描述
打印的形参如下图:
在这里插入图片描述
4、表头里的排序功能失效,无法点击进行排序功能
在这里插入图片描述

上诉背景已说过,我已经把这个获取拖拽的方法封装成一个全局的方法。而出现这个问题的直接原因也正因如此,我把 resizeableTitle 封装成一个全局的 getTableDragHeader 方法,并放在一个 js 文件里面,而不是像官方文档写在调用表格组件的 vue 页面里面。因为不封装的话,那所有需要拖拽的表格都要重复写这些代码,这就很不方便,所以我就把它写成了一个全局的方法,供所有带有表格的页面使用。

除此之外我还发现了一个问题,在我封装的全局拖拽方法里,如果我把 resizeableTitle 改成其它名字,那么调用的方式就会不一样,原本 resizeableTitle 命名的函数只传一个参数过来,如下图:
在这里插入图片描述
打印 a 形参:
在这里插入图片描述
只写一个 a 形参就能拿到 props、children、data。

但改成其它任意名字的函数名,就传三个参数过来,官方文档的写法就是传三个参数的。
打印如下图,第一个参数是个function,第二个参数是props,第三个参数是children:
在这里插入图片描述
这就很莫名其妙,只是函数名字不一样就有这么大的区别。
原本使用 resizeableTitle 封装成全局的方法去调用获取可拖拽headers 的colums 是不会报错,只是排序功能无法点击。但当我改 resizeableTitle 的名字为 resizeable 后,就是变成和官方文档有三个参数的情况时,控制台就报错:Error in render: “ReferenceError: h is not defined”,如下图:
在这里插入图片描述
百度了下这个错误,其他人遇到这个问题,它们的解释是表格 colums 没有放在 data 函数里面,获取不到上下文,这是什么鬼模糊的解释。。。
这和我现在的情况关联起来,的确也和上下文有关,毕竟我封装成全局的 js 方法,也就是方法不是特属某一个 vue 实例里的,而是全局的 js 方法。

所以我还是先把方法改成局部的,再对比看看到底所谓的上下文到底是什么?
按 antd 文档那种形式,我把方法写回在 vue 页面里面,getTableDragHeader 就是我封装成一个能根据 colums 返回拖拽header的方法,代码如下图:
在这里插入图片描述

然后去看,结果是不会出现排序功能失效的问题。那也就是说不是因为表头被 components 返回的 th 模板替换导致的事件失效,vue-draggable-resizable + components 的拖拽方法是没问题的。

所以问题可能出现在方法的调用和声明上了。

经过很多次的尝试,考虑过this的指向、引入的vue实例对象方式,但似乎都没关系,重点好像是方法必须在组件里声明,必须得要在调用的组件实例下。那这样还怎么封装成全局的方法供所有组件使用?不是只能在一个一个组件里面写了吗?

我回过头,重新点开报错地方,直接查看他的源码:
在这里插入图片描述
好像是 TableHeaderRow 是提供 render 给 vue 来渲染函数 return template 的方式,其中的 h 就是创建元素的方法。
综上所述,按我的理解,其实就是 return 返回的模板,如下图:
在这里插入图片描述
必须得要在 vue 实例里面写,而不是单纯的写在一个 js 文件,然后才在 vue 里面调用。百度时,网友说这种报错的解决方式是 colums 写在 data 里面,也无非是 它们的 colums 带有 customRender 这种需要渲染模板的选项而已。所以类似我封装的 getTableDragHeader 方法,这种带有 template 模板返回的方法,是不能在 vue 实例外声明的,必须得在某一个 vue 实例中声明。所以官方文档中在 vue 实例外声明,如下图,也是有问题的。
在这里插入图片描述
官方控制台不报错,是因为函数名是 ResizeableTitle ,改成其它函数名会立即报错,而且还是会出现上述的 h is not defined 报错,因为返回模板的方法没有写在 vue 实例里面。
解决:
既然知道必须得将方法写在任意 vue 实例里面,但是我又不想一个一个声明,所以我得要有一个全局的 vue 实例可供所有 vue 页面调用。这就想到了 $root 根组件 和 $Bus 。因为不想让 $root 变得复杂和冗乱,所以就创建了一个 $Bus 公共 vue 实例,用于提供给全部组件调用和通讯。
代码如下:

import Vue from 'vue';
import VueDraggableResizable from 'vue-draggable-resizable';
Vue.component('vue-draggable-resizable', VueDraggableResizable);
const Bus = new Vue({
    
    
    methods:{
    
    
        getTableDragHeader(columns) {
    
    
            const draggingMap = {
    
    };
            columns.forEach((col) => {
    
    
              draggingMap[col.key] = col.width;
            });
            const draggingState = Vue.observable(draggingMap);
            const resizeable  = (a,b,c) => {
    
    
              let thDom = null;
              const props = b;
              const children = c;
              const {
    
     key, ...restProps } = props;
              let col = null;
              if (key === "selection-column") {
    
    
                // 当前column为全选的时候,要返回全选的 th ,否则无法出现全选
                return <th {
    
    ...restProps} class={
    
    props.class}>{
    
    children}</th>
              } else {
    
    
                col = columns.find((item) => {
    
    
                  const k = item.dataIndex || item.key;
                  return k === key;
                });
              }
          
              if (!col.width) {
    
    
                return <th {
    
    ...restProps} class={
    
    props.class}>{
    
    children}</th>;
              }
              const onDrag = (x) => {
    
    
                draggingState[key] = 0;
                const maxWidth = thDom.parentNode.offsetWidth/2 || 100; // 拖拽最长长度不能超过表格的一半
                col.width = Math.min(Math.max(x, 50), maxWidth);
              };
          
              const onDragstop = () => {
    
    
                draggingState[key] = thDom.getBoundingClientRect().width;
              };
              return (
                <th {
    
    ...restProps} v-ant-ref={
    
    (r) => (thDom = r)} width={
    
    col.width} class={
    
    props.class+" resize-table-th"}>
                  {
    
    children}
                  <vue-draggable-resizable
                    key={
    
    col.key}
                    class="table-draggable-handle"
                    w={
    
    10}
                    x={
    
    draggingState[key] || col.width}
                    z={
    
    1}
                    axis="x"
                    draggable={
    
    true}
                    resizable={
    
    false}
                    onDragging={
    
    onDrag}
                    onDragstop={
    
    onDragstop}
                  ></vue-draggable-resizable>
                </th>
              );
            };
            return {
    
    
              header: {
    
    
                cell: resizeable ,
              },
            }
          }
    }
})

export default Bus;

我将 getTableDragHeader 方法写在了 Bus 这个 vue 实例下面。
最后我在 main.js 中,引入上诉导出的 Bus 并挂载在 Vue 实例下成为 $Bus,其实 $Bus.getTableDragHeader 就可以调用获取可拖拽的 header colums,但因为之前封装的方法 $YGetTableDragHeader,代码很多地方已经用 $YGetTableDragHeader 的形式调用获取了,所以只能把 $Bus.getTableDragHeader 这个新方法再重新赋值给它,那其它地方的代码就不用改了。
在这里插入图片描述

终言

antd 表格的 components 文档可查资料很少,表格列之间的伸缩文档也只是给了一个小例子,而且这个例子的问题很多,在我看来本身就是有问题的。希望这篇文章可以提供给大家作一个参考并解决遇到的问题,若使用 vue-draggable-resizable 有其它问题,可给我留言,我看到也会帮忙解决的。

猜你喜欢

转载自blog.csdn.net/weixin_43589827/article/details/119446846