美文网首页
踩坑日记:在ant-design-vue的table组件中集成v

踩坑日记:在ant-design-vue的table组件中集成v

作者: 喜剧之王爱创作 | 来源:发表于2020-11-03 22:22 被阅读0次

    标题有点长,就如这踩坑的时间,那么就进入这次的踩坑日记。在Vue的Table组件中,实现可伸缩列,如果你使用的是Element-Ui那么这是一个现成的功能,如果你使用的是ant-design-vue,那么是需要集成一个vue-draggable-resizable插件的。详细使用这里不用多说,我想大多说开发者是会先把文档中的用法跑一下的,这期的坑就是,这个Demo跑不起来!其中有这样几点肯,我来记录一下。
    • Module parse failed: Argument name clash...
    • 表格没有拖动滑块
    • 拖动时不够流畅且宽度乱跳
    • 一些细节处理

    下面就来一一解答一下问题。我们先将官方文档的代码拷贝下来

    <template>
      <a-table bordered :columns="columns" :components="components" :data-source="data">
        <template v-slot:action>
          <a href="javascript:;">Delete</a>
        </template>
      </a-table>
    </template>
    
    <script>
    import Vue from 'vue';
    import VueDraggableResizable from 'vue-draggable-resizable';
    
    Vue.component('vue-draggable-resizable', VueDraggableResizable);
    const columns = [
      {
        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',
        scopedSlots: { customRender: 'action' },
      },
    ];
    const data = [
      {
        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',
      },
    ];
    const draggingMap = {};
    columns.forEach(col => {
      draggingMap[col.key] = col.width;
    });
    const draggingState = Vue.observable(draggingMap);
    const ResizeableTitle = (h, props, children) => {
      let thDom = null;
      const { key, ...restProps } = props;
      const col = columns.find(col => {
        const k = col.dataIndex || col.key;
        return k === key;
      });
      if (!col.width) {
        return <th {...restProps}>{children}</th>;
      }
      const onDrag = x => {
        draggingState[key] = 0;
        col.width = Math.max(x, 1);
      };
    
      const onDragstop = () => {
        draggingState[key] = thDom.getBoundingClientRect().width;
      };
      return (
        <th {...restProps} v-ant-ref={r => (thDom = r)} width={col.width} 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>
      );
    };
    export default {
      name: 'App',
      data() {
        this.components = {
          header: {
            cell: ResizeableTitle,
          },
        };
        return {
          data,
          columns,
        };
      },
    };
    </script>
    <style lang="less">
    .resize-table-th {
      position: relative;
      .table-draggable-handle {
        height: 100% !important;
        bottom: 0;
        left: auto !important;
        right: -5px;
        cursor: col-resize;
        touch-action: none;
      }
    }
    </style>
    
    • 如果你按照上面的文档操作了,那么你第一个一定会遇到

    Module parse failed: Argument name clash

    其意思是命名发生了冲突,我们有两种方案解决这个问题,

    1. propschildren提出来,并给h变量起一个别名。大概代码如下,当然取值的时候,你也可以使用ES6解构。
    const ResizeableTitle = (h1) => {
      const props = h1.props
      const children = h1.children
    
    1. 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那样流畅。于是我开始读他的代码,大致找出以下毛病
    1. columns的Item中不一定都是key,(大部分不是key)
    columns.forEach(col => {
      draggingMap[col.key] = col.width;
    });
    

    原代码如上,意思是遍历columns,取其每一项的宽度为值,每一项的key为键名,生成对象draggingMap,而实际开发中,columns的每一项中dataIndexkey一般是二选一的,虽然也有共存的情况(没必要也不推荐),但大多数还是二选一,就像原代码中的columns那样,他定义的都是dataIndex,而它代码中却取key,这显然是不严谨的。我们需要把代码改成这样

        columns.forEach((col) => {
          const k = col.dataIndex || col.key;
          draggingMap[k] = col.width;
        });
    

    这样才能生成完整的draggingMap对象,当然,这个对代码功能没有实质性影响,因为后续对draggingMap的取值中,并不是操作每一个值,而是靠set的方式设置值,所以技术这个对象是一个残缺的,但不影响,我想着也导致作者直接没注意到这里写错了吧。至于何种残缺,自己打断点看,大概就是{undefined: 90, index: 80}类似这样的对象。接着往下看

    1. 宽度设置有误
          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.widthdraggingState[key]的来历和含义都明白了,其中,我们在初始化的时候,取col.width生成了draggingState[key]。这个时候col.width正是我们在设置columns时意为设置列宽的值,但是拖动过后,col.width表示成了拖动时鼠标的终止位置,而这个位置不能再代表宽度了,这时候draggingState[key]还是columns中宽度的映射,他始终是没有变化的,所以我们应该将draggingState[key]当成列宽来使用。所以做下面的改动

            <th
              {...restProps}
              v-ant-ref={(r) => { 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] }才对。
    然后

    <vue-draggable-resizable
         key={col.dataIndex || col.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 <th {...restProps}>{children}</th>;
          }
          const onDrag = (x) => {
            draggingState[key] = 0;
            col.width = Math.max(x, 1);
          };
          const onDragstop = () => {
            draggingState[key] = thDom.getBoundingClientRect().width;
          };
          return (
            <th
              {...restProps}
              v-ant-ref={(r) => { thDom = r; }}
              width={draggingState[key]}
              class="resize-table-th"
            >
              {children}
              <vue-draggable-resizable
                key={col.dataIndex || col.key}
                class="table-draggable-handle"
                w={10}
                x={ col.width || draggingState[key] }
                z={1}
                axis="x"
                draggable={true}
                resizable={false}
                onDragging={onDrag}
                onDragstop={onDragstop}
              ></vue-draggable-resizable>
            </th>
          );
        };
        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日更新。

    相关文章

      网友评论

          本文标题:踩坑日记:在ant-design-vue的table组件中集成v

          本文链接:https://www.haomeiwen.com/subject/ayiivktx.html