踩坑日记:在ant-design-vue的table组件中集成vue-draggable-resizable实现可伸缩列遇到的坑


标题有点长,就如这踩坑的时间,那么就进入这次的踩坑日记。在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日更新。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339