From 50261e0236baf27cc022e08014f1334e13104b0c Mon Sep 17 00:00:00 2001
From: tjz <415800467@qq.com>
Date: Fri, 30 Mar 2018 22:00:55 +0800
Subject: [PATCH] update table

---
 components/table/SelectionBox.jsx         |  75 +++++++
 components/table/SelectionBox.tsx         |  68 -------
 components/table/SelectionCheckboxAll.jsx | 182 +++++++++++++++++
 components/table/SelectionCheckboxAll.tsx | 183 -----------------
 components/table/filterDropdown.jsx       | 229 ++++++++++++++++++++++
 components/table/filterDropdown.tsx       | 228 ---------------------
 package-lock.json                         |  13 ++
 package.json                              |   1 +
 8 files changed, 500 insertions(+), 479 deletions(-)
 create mode 100644 components/table/SelectionBox.jsx
 delete mode 100644 components/table/SelectionBox.tsx
 create mode 100644 components/table/SelectionCheckboxAll.jsx
 delete mode 100644 components/table/SelectionCheckboxAll.tsx
 create mode 100755 components/table/filterDropdown.jsx
 delete mode 100755 components/table/filterDropdown.tsx

diff --git a/components/table/SelectionBox.jsx b/components/table/SelectionBox.jsx
new file mode 100644
index 000000000..b29b85baf
--- /dev/null
+++ b/components/table/SelectionBox.jsx
@@ -0,0 +1,75 @@
+
+import Checkbox from '../checkbox'
+import Radio from '../radio'
+import { SelectionBoxProps } from './interface'
+import BaseMixin from '../_util/BaseMixin'
+import { getOptionProps } from '../_util/props-util'
+
+export default {
+  mixins: [BaseMixin],
+  name: 'SelectionBox',
+  props: SelectionBoxProps,
+  data () {
+    return {
+      checked: this.getCheckState(this.$props),
+    }
+  },
+
+  mounted () {
+    this.subscribe()
+  },
+
+  beforeDestroy () {
+    if (this.unsubscribe) {
+      this.unsubscribe()
+    }
+  },
+  methods: {
+    subscribe () {
+      const { store } = this
+      this.unsubscribe = store.subscribe(() => {
+        const checked = this.getCheckState(this.$props)
+        this.setState({ checked })
+      })
+    },
+
+    getCheckState (props) {
+      const { store, defaultSelection, rowIndex } = props
+      let checked = false
+      if (store.getState().selectionDirty) {
+        checked = store.getState().selectedRowKeys.indexOf(rowIndex) >= 0
+      } else {
+        checked = (store.getState().selectedRowKeys.indexOf(rowIndex) >= 0 ||
+                   defaultSelection.indexOf(rowIndex) >= 0)
+      }
+      return checked
+    },
+  },
+
+  render () {
+    const { type, rowIndex, ...rest } = getOptionProps(this)
+    const { checked, $attrs, $listeners } = this
+    const checkboxProps = {
+      props: {
+        checked,
+        ...rest,
+      },
+      attrs: $attrs,
+      on: $listeners,
+    }
+    if (type === 'radio') {
+      checkboxProps.props.value = rowIndex
+      return (
+        <Radio
+          {...checkboxProps}
+        />
+      )
+    } else {
+      return (
+        <Checkbox
+          {...checkboxProps}
+        />
+      )
+    }
+  },
+}
diff --git a/components/table/SelectionBox.tsx b/components/table/SelectionBox.tsx
deleted file mode 100644
index 17a6f0b60..000000000
--- a/components/table/SelectionBox.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import * as React from 'react';
-import Checkbox from '../checkbox';
-import Radio from '../radio';
-import { SelectionBoxProps, SelectionBoxState } from './interface';
-
-export default class SelectionBox extends React.Component<SelectionBoxProps, SelectionBoxState> {
-  unsubscribe: () => void;
-
-  constructor(props: SelectionBoxProps) {
-    super(props);
-
-    this.state = {
-      checked: this.getCheckState(props),
-    };
-  }
-
-  componentDidMount() {
-    this.subscribe();
-  }
-
-  componentWillUnmount() {
-    if (this.unsubscribe) {
-      this.unsubscribe();
-    }
-  }
-
-  subscribe() {
-    const { store } = this.props;
-    this.unsubscribe = store.subscribe(() => {
-      const checked = this.getCheckState(this.props);
-      this.setState({ checked });
-    });
-  }
-
-  getCheckState(props: SelectionBoxProps) {
-    const { store, defaultSelection, rowIndex } = props;
-    let checked = false;
-    if (store.getState().selectionDirty) {
-      checked = store.getState().selectedRowKeys.indexOf(rowIndex) >= 0;
-    } else {
-      checked = (store.getState().selectedRowKeys.indexOf(rowIndex) >= 0 ||
-                 defaultSelection.indexOf(rowIndex) >= 0);
-    }
-    return checked;
-  }
-
-  render() {
-    const { type, rowIndex, ...rest } = this.props;
-    const { checked } = this.state;
-
-    if (type === 'radio') {
-      return (
-        <Radio
-          checked={checked}
-          value={rowIndex}
-          {...rest}
-        />
-      );
-    } else {
-      return (
-        <Checkbox
-          checked={checked}
-          {...rest}
-        />
-      );
-    }
-  }
-}
diff --git a/components/table/SelectionCheckboxAll.jsx b/components/table/SelectionCheckboxAll.jsx
new file mode 100644
index 000000000..83e9b3abc
--- /dev/null
+++ b/components/table/SelectionCheckboxAll.jsx
@@ -0,0 +1,182 @@
+import Checkbox from '../checkbox'
+import Dropdown from '../dropdown'
+import Menu from '../menu'
+import Icon from '../icon'
+import classNames from 'classnames'
+import { SelectionCheckboxAllProps } from './interface'
+import BaseMixin from '../_util/BaseMixin'
+
+export default {
+  props: SelectionCheckboxAllProps,
+  name: 'SelectionCheckboxAll',
+  mixins: [BaseMixin],
+  data () {
+    const { $props: props } = this
+    this.defaultSelections = props.hideDefaultSelections ? [] : [{
+      key: 'all',
+      text: props.locale.selectAll,
+      onSelect: () => {},
+    }, {
+      key: 'invert',
+      text: props.locale.selectInvert,
+      onSelect: () => {},
+    }]
+
+    this.state = {
+      checked: this.getCheckState(props),
+      indeterminate: this.getIndeterminateState(props),
+    }
+  },
+
+  mounted () {
+    this.subscribe()
+  },
+
+  componentWillReceiveProps (nextProps) {
+    this.setCheckState()
+  },
+
+  beforeDestroy () {
+    if (this.unsubscribe) {
+      this.unsubscribe()
+    }
+  },
+  methods: {
+    subscribe () {
+      const { store } = this
+      this.unsubscribe = store.subscribe(() => {
+        this.setCheckState()
+      })
+    },
+
+    checkSelection (data, type, byDefaultChecked) {
+      const { store, getCheckboxPropsByItem, getRecordKey } = this
+      // type should be 'every' | 'some'
+      if (type === 'every' || type === 'some') {
+        return (
+          byDefaultChecked
+            ? data[type]((item, i) => getCheckboxPropsByItem(item, i).defaultChecked)
+            : data[type]((item, i) =>
+              store.getState().selectedRowKeys.indexOf(getRecordKey(item, i)) >= 0)
+        )
+      }
+      return false
+    },
+
+    setCheckState () {
+      const checked = this.getCheckState()
+      const indeterminate = this.getIndeterminateState()
+      if (checked !== this.checked) {
+        this.setState({ checked })
+      }
+      if (indeterminate !== this.indeterminate) {
+        this.setState({ indeterminate })
+      }
+    },
+
+    getCheckState () {
+      const { store, data } = this
+      let checked
+      if (!data.length) {
+        checked = false
+      } else {
+        checked = store.getState().selectionDirty
+          ? this.checkSelection(data, 'every', false)
+          : (
+            this.checkSelection(data, 'every', false) ||
+            this.checkSelection(data, 'every', true)
+          )
+      }
+      return checked
+    },
+
+    getIndeterminateState () {
+      const { store, data } = this
+      let indeterminate
+      if (!data.length) {
+        indeterminate = false
+      } else {
+        indeterminate = store.getState().selectionDirty
+          ? (
+            this.checkSelection(data, 'some', false) &&
+              !this.checkSelection(data, 'every', false)
+          )
+          : ((this.checkSelection(data, 'some', false) &&
+              !this.checkSelection(data, 'every', false)) ||
+              (this.checkSelection(data, 'some', true) &&
+              !this.checkSelection(data, 'every', true))
+          )
+      }
+      return indeterminate
+    },
+
+    handleSelectAllChagne (e) {
+      const checked = e.target.checked
+      this.$emit('select', checked ? 'all' : 'removeAll', 0, null)
+    },
+
+    renderMenus (selections) {
+      return selections.map((selection, index) => {
+        return (
+          <Menu.Item
+            key={selection.key || index}
+          >
+            <div
+              onClick={() => { this.$emit('select', selection.key, index, selection.onSelect) }}
+            >
+              {selection.text}
+            </div>
+          </Menu.Item>
+        )
+      })
+    },
+  },
+
+  render () {
+    const { disabled, prefixCls, selections, getPopupContainer, checked, indeterminate } = this
+
+    const selectionPrefixCls = `${prefixCls}-selection`
+
+    let customSelections = null
+
+    if (selections) {
+      const newSelections = Array.isArray(selections) ? this.defaultSelections.concat(selections)
+        : this.defaultSelections
+
+      const menu = (
+        <Menu
+          class={`${selectionPrefixCls}-menu`}
+          selectedKeys={[]}
+        >
+          {this.renderMenus(newSelections)}
+        </Menu>
+      )
+
+      customSelections = newSelections.length > 0 ? (
+        <Dropdown
+          getPopupContainer={getPopupContainer}
+        >
+          <template slot='overlay'>
+            {menu}
+          </template>
+          <div class={`${selectionPrefixCls}-down`}>
+            <Icon type='down' />
+          </div>
+        </Dropdown>
+      ) : null
+    }
+
+    return (
+      <div class={selectionPrefixCls}>
+        <Checkbox
+          class={classNames({ [`${selectionPrefixCls}-select-all-custom`]: customSelections })}
+          checked={checked}
+          indeterminate={indeterminate}
+          disabled={disabled}
+          onChange={this.handleSelectAllChagne}
+        />
+        {customSelections}
+      </div>
+    )
+  },
+}
diff --git a/components/table/SelectionCheckboxAll.tsx b/components/table/SelectionCheckboxAll.tsx
deleted file mode 100644
index 8386d1af7..000000000
--- a/components/table/SelectionCheckboxAll.tsx
+++ /dev/null
@@ -1,183 +0,0 @@
-import * as React from 'react';
-import Checkbox from '../checkbox';
-import Dropdown from '../dropdown';
-import Menu from '../menu';
-import Icon from '../icon';
-import classNames from 'classnames';
-import { SelectionCheckboxAllProps, SelectionCheckboxAllState, SelectionItem } from './interface';
-
-export default class SelectionCheckboxAll<T> extends
-  React.Component<SelectionCheckboxAllProps<T>, SelectionCheckboxAllState> {
-  unsubscribe: () => void;
-  defaultSelections: SelectionItem[];
-
-  constructor(props: SelectionCheckboxAllProps<T>) {
-    super(props);
-
-    this.defaultSelections = props.hideDefaultSelections ? [] : [{
-      key: 'all',
-      text: props.locale.selectAll,
-      onSelect: () => {},
-    }, {
-      key: 'invert',
-      text: props.locale.selectInvert,
-      onSelect: () => {},
-    }];
-
-    this.state = {
-      checked: this.getCheckState(props),
-      indeterminate: this.getIndeterminateState(props),
-    };
-  }
-
-  componentDidMount() {
-    this.subscribe();
-  }
-
-  componentWillReceiveProps(nextProps: SelectionCheckboxAllProps<T>) {
-    this.setCheckState(nextProps);
-  }
-
-  componentWillUnmount() {
-    if (this.unsubscribe) {
-      this.unsubscribe();
-    }
-  }
-
-  subscribe() {
-    const { store } = this.props;
-    this.unsubscribe = store.subscribe(() => {
-      this.setCheckState(this.props);
-    });
-  }
-
-  checkSelection(data: T[], type: string, byDefaultChecked: boolean) {
-    const { store, getCheckboxPropsByItem, getRecordKey } = this.props;
-    // type should be 'every' | 'some'
-    if (type === 'every' || type === 'some') {
-      return (
-        byDefaultChecked
-        ? data[type]((item, i) => getCheckboxPropsByItem(item, i).defaultChecked)
-        : data[type]((item, i) =>
-              store.getState().selectedRowKeys.indexOf(getRecordKey(item, i)) >= 0)
-      );
-    }
-    return false;
-  }
-
-  setCheckState(props: SelectionCheckboxAllProps<T>) {
-    const checked = this.getCheckState(props);
-    const indeterminate = this.getIndeterminateState(props);
-    if (checked !== this.state.checked) {
-      this.setState({ checked });
-    }
-    if (indeterminate !== this.state.indeterminate) {
-      this.setState({ indeterminate });
-    }
-  }
-
-  getCheckState(props: SelectionCheckboxAllProps<T>) {
-    const { store, data } = props;
-    let checked;
-    if (!data.length) {
-      checked = false;
-    } else {
-      checked = store.getState().selectionDirty
-        ? this.checkSelection(data, 'every', false)
-        : (
-          this.checkSelection(data, 'every', false) ||
-          this.checkSelection(data, 'every', true)
-        );
-
-    }
-    return checked;
-  }
-
-  getIndeterminateState(props: SelectionCheckboxAllProps<T>) {
-    const { store, data } = props;
-    let indeterminate;
-    if (!data.length) {
-      indeterminate = false;
-    } else {
-      indeterminate = store.getState().selectionDirty
-        ? (
-          this.checkSelection(data, 'some', false) &&
-            !this.checkSelection(data, 'every', false)
-        )
-        : ((this.checkSelection(data, 'some', false) &&
-            !this.checkSelection(data, 'every', false)) ||
-            (this.checkSelection(data, 'some', true) &&
-            !this.checkSelection(data, 'every', true))
-          );
-    }
-    return indeterminate;
-  }
-
-  handleSelectAllChagne = (e: React.ChangeEvent<HTMLInputElement>) => {
-    let checked = e.target.checked;
-    this.props.onSelect(checked ? 'all' : 'removeAll', 0, null);
-  }
-
-  renderMenus(selections: SelectionItem[]) {
-    return selections.map((selection, index) => {
-      return (
-        <Menu.Item
-          key={selection.key || index}
-        >
-          <div
-            onClick={() => {this.props.onSelect(selection.key, index, selection.onSelect); }}
-          >
-            {selection.text}
-          </div>
-        </Menu.Item>
-      );
-    });
-  }
-
-  render() {
-    const { disabled, prefixCls, selections, getPopupContainer } = this.props;
-    const { checked, indeterminate } = this.state;
-
-    let selectionPrefixCls = `${prefixCls}-selection`;
-
-    let customSelections: React.ReactNode = null;
-
-    if (selections) {
-      let newSelections = Array.isArray(selections) ? this.defaultSelections.concat(selections)
-      : this.defaultSelections;
-
-      const menu = (
-        <Menu
-          className={`${selectionPrefixCls}-menu`}
-          selectedKeys={[]}
-        >
-          {this.renderMenus(newSelections)}
-        </Menu>
-      );
-
-      customSelections = newSelections.length > 0 ? (
-        <Dropdown
-          overlay={menu}
-          getPopupContainer={getPopupContainer}
-        >
-          <div className={`${selectionPrefixCls}-down`}>
-            <Icon type="down" />
-          </div>
-        </Dropdown>
-      ) : null;
-    }
-
-    return (
-      <div className={selectionPrefixCls}>
-        <Checkbox
-          className={classNames({ [`${selectionPrefixCls}-select-all-custom`]: customSelections })}
-          checked={checked}
-          indeterminate={indeterminate}
-          disabled={disabled}
-          onChange={this.handleSelectAllChagne}
-        />
-        {customSelections}
-      </div>
-    );
-  }
-}
diff --git a/components/table/filterDropdown.jsx b/components/table/filterDropdown.jsx
new file mode 100755
index 000000000..cbc15e465
--- /dev/null
+++ b/components/table/filterDropdown.jsx
@@ -0,0 +1,229 @@
+
+import Menu, { SubMenu, Item as MenuItem } from '../vc-menu'
+import closest from 'dom-closest'
+import classNames from 'classnames'
+import Dropdown from '../dropdown'
+import Icon from '../icon'
+import Checkbox from '../checkbox'
+import Radio from '../radio'
+import FilterDropdownMenuWrapper from './FilterDropdownMenuWrapper'
+import { FilterMenuProps } from './interface'
+import { initDefaultProps, cloneElement } from '../_util/props-util'
+import BaseMixin from '../_util/BaseMixin'
+
+export default {
+  mixins: [BaseMixin],
+  name: 'FilterMenu',
+  props: initDefaultProps(FilterMenuProps, {
+    handleFilter () {},
+    column: {},
+  }),
+
+  data () {
+    const visible = ('filterDropdownVisible' in this.column)
+      ? this.column.filterDropdownVisible : false
+
+    return {
+      sSelectedKeys: this.selectedKeys,
+      sKeyPathOfSelectedItem: {}, // 记录所有有选中子菜单的祖先菜单
+      sVisible: visible,
+    }
+  },
+
+  mounted () {
+    const { column } = this
+    this.$nextTick(() => {
+      this.setNeverShown(column)
+    })
+  },
+
+  componentWillReceiveProps (nextProps) {
+    const { column } = nextProps
+    this.setNeverShown(column)
+    const newState = {}
+    if ('selectedKeys' in nextProps) {
+      newState.selectedKeys = nextProps.selectedKeys
+    }
+    if ('filterDropdownVisible' in column) {
+      newState.visible = column.filterDropdownVisible
+    }
+    if (Object.keys(newState).length > 0) {
+      this.setState(newState)
+    }
+  },
+  methods: {
+
+  },
+
+  setNeverShown  (column) {
+    const rootNode = this.$el
+    const filterBelongToScrollBody = !!closest(rootNode, `.ant-table-scroll`)
+    if (filterBelongToScrollBody) {
+      // When fixed column have filters, there will be two dropdown menus
+      // Filter dropdown menu inside scroll body should never be shown
+      // To fix https://github.com/ant-design/ant-design/issues/5010 and
+      // https://github.com/ant-design/ant-design/issues/7909
+      this.neverShown = !!column.fixed
+    }
+  },
+
+  setSelectedKeys ({ selectedKeys }) {
+    this.setState({ sSelectedKeys: selectedKeys })
+  },
+
+  setVisible (visible) {
+    const { column } = this
+    if (!('filterDropdownVisible' in column)) {
+      this.setState({ sVisible: visible })
+    }
+    if (column.onFilterDropdownVisibleChange) {
+      column.onFilterDropdownVisibleChange(visible)
+    }
+  },
+
+  handleClearFilters () {
+    this.setState({
+      sSelectedKeys: [],
+    }, this.handleConfirm)
+  },
+
+  handleConfirm  () {
+    this.setVisible(false)
+    this.confirmFilter()
+  },
+
+  onVisibleChange  (visible) {
+    this.setVisible(visible)
+    if (!visible) {
+      this.confirmFilter()
+    }
+  },
+
+  confirmFilter () {
+    if (this.sSelectedKeys !== this.selectedKeys) {
+      this.confirmFilter(this.column, this.sSelectedKeys)
+    }
+  },
+
+  renderMenuItem (item) {
+    const { column } = this
+    const multiple = ('filterMultiple' in column) ? column.filterMultiple : true
+    const input = multiple ? (
+      <Checkbox checked={this.sSelectedKeys.indexOf(item.value.toString()) >= 0} />
+    ) : (
+      <Radio checked={this.sSelectedKeys.indexOf(item.value.toString()) >= 0} />
+    )
+
+    return (
+      <MenuItem key={item.value}>
+        {input}
+        <span>{item.text}</span>
+      </MenuItem>
+    )
+  },
+
+  hasSubMenu () {
+    const { column: { filters = [] }} = this
+    return filters.some(item => !!(item.children && item.children.length > 0))
+  },
+
+  renderMenus (items) {
+    return items.map(item => {
+      if (item.children && item.children.length > 0) {
+        const { sKeyPathOfSelectedItem } = this
+        const containSelected = Object.keys(sKeyPathOfSelectedItem).some(
+          key => sKeyPathOfSelectedItem[key].indexOf(item.value) >= 0,
+        )
+        const subMenuCls = containSelected ? `${this.dropdownPrefixCls}-submenu-contain-selected` : ''
+        return (
+          <SubMenu title={item.text} class={subMenuCls} key={item.value.toString()}>
+            {this.renderMenus(item.children)}
+          </SubMenu>
+        )
+      }
+      return this.renderMenuItem(item)
+    })
+  },
+
+  handleMenuItemClick (info) {
+    if (info.keyPath.length <= 1) {
+      return
+    }
+    const keyPathOfSelectedItem = this.sKeyPathOfSelectedItem
+    if (this.sSelectedKeys.indexOf(info.key) >= 0) {
+      // deselect SubMenu child
+      delete keyPathOfSelectedItem[info.key]
+    } else {
+      // select SubMenu child
+      keyPathOfSelectedItem[info.key] = info.keyPath
+    }
+    this.setState({ keyPathOfSelectedItem })
+  },
+
+  renderFilterIcon () {
+    const { column, locale, prefixCls } = this
+    const filterIcon = column.filterIcon
+    const dropdownSelectedClass = this.selectedKeys.length > 0 ? `${prefixCls}-selected` : ''
+
+    return filterIcon ? cloneElement(filterIcon, {
+      title: locale.filterTitle,
+      className: classNames(filterIcon.className, {
+        [`${prefixCls}-icon`]: true,
+      }),
+    }) : <Icon title={locale.filterTitle} type='filter' class={dropdownSelectedClass} />
+  },
+  render () {
+    const { column, locale, prefixCls, dropdownPrefixCls, getPopupContainer } = this
+    // default multiple selection in filter dropdown
+    const multiple = ('filterMultiple' in column) ? column.filterMultiple : true
+    const dropdownMenuClass = classNames({
+      [`${dropdownPrefixCls}-menu-without-submenu`]: !this.hasSubMenu(),
+    })
+    const menus = column.filterDropdown ? (
+      <FilterDropdownMenuWrapper>
+        {column.filterDropdown}
+      </FilterDropdownMenuWrapper>
+    ) : (
+      <FilterDropdownMenuWrapper class={`${prefixCls}-dropdown`}>
+        <Menu
+          multiple={multiple}
+          onClick={this.handleMenuItemClick}
+          prefixCls={`${dropdownPrefixCls}-menu`}
+          class={dropdownMenuClass}
+          onSelect={this.setSelectedKeys}
+          onDeselect={this.setSelectedKeys}
+          selectedKeys={this.sSelectedKeys}
+        >
+          {this.renderMenus(column.filters)}
+        </Menu>
+        <div class={`${prefixCls}-dropdown-btns`}>
+          <a
+            class={`${prefixCls}-dropdown-link confirm`}
+            onClick={this.handleConfirm}
+          >
+            {locale.filterConfirm}
+          </a>
+          <a
+            class={`${prefixCls}-dropdown-link clear`}
+            onClick={this.handleClearFilters}
+          >
+            {locale.filterReset}
+          </a>
+        </div>
+      </FilterDropdownMenuWrapper>
+    )
+
+    return (
+      <Dropdown
+        trigger={['click']}
+        visible={this.neverShown ? false : this.sVisible}
+        onVisibleChange={this.onVisibleChange}
+        getPopupContainer={getPopupContainer}
+        forceRender
+      >
+        <template slot='overlay'>{menus}</template>
+        {this.renderFilterIcon()}
+      </Dropdown>
+    )
+  },
+}
diff --git a/components/table/filterDropdown.tsx b/components/table/filterDropdown.tsx
deleted file mode 100755
index 5ff8eb0f2..000000000
--- a/components/table/filterDropdown.tsx
+++ /dev/null
@@ -1,228 +0,0 @@
-import * as React from 'react';
-import * as ReactDOM from 'react-dom';
-import Menu, { SubMenu, Item as MenuItem } from 'rc-menu';
-import closest from 'dom-closest';
-import classNames from 'classnames';
-import Dropdown from '../dropdown';
-import Icon from '../icon';
-import Checkbox from '../checkbox';
-import Radio from '../radio';
-import FilterDropdownMenuWrapper from './FilterDropdownMenuWrapper';
-import { FilterMenuProps, FilterMenuState, ColumnProps, ColumnFilterItem } from './interface';
-
-export default class FilterMenu<T> extends React.Component<FilterMenuProps<T>, FilterMenuState> {
-  static defaultProps = {
-    handleFilter() {},
-    column: {},
-  };
-
-  neverShown: boolean;
-
-  constructor(props: FilterMenuProps<T>) {
-    super(props);
-
-    const visible = ('filterDropdownVisible' in props.column) ?
-      props.column.filterDropdownVisible : false;
-
-    this.state = {
-      selectedKeys: props.selectedKeys,
-      keyPathOfSelectedItem: {},    // 记录所有有选中子菜单的祖先菜单
-      visible,
-    };
-  }
-
-  componentDidMount() {
-    const { column } = this.props;
-    this.setNeverShown(column);
-  }
-
-  componentWillReceiveProps(nextProps: FilterMenuProps<T>) {
-    const { column } = nextProps;
-    this.setNeverShown(column);
-    const newState = {} as {
-      selectedKeys: string[];
-      visible: boolean;
-    };
-    if ('selectedKeys' in nextProps) {
-      newState.selectedKeys = nextProps.selectedKeys;
-    }
-    if ('filterDropdownVisible' in column) {
-      newState.visible = column.filterDropdownVisible as boolean;
-    }
-    if (Object.keys(newState).length > 0) {
-      this.setState(newState);
-    }
-  }
-
-  setNeverShown = (column: ColumnProps<T>) => {
-    const rootNode = ReactDOM.findDOMNode(this);
-    const filterBelongToScrollBody = !!closest(rootNode, `.ant-table-scroll`);
-    if (filterBelongToScrollBody) {
-      // When fixed column have filters, there will be two dropdown menus
-      // Filter dropdown menu inside scroll body should never be shown
-      // To fix https://github.com/ant-design/ant-design/issues/5010 and
-      // https://github.com/ant-design/ant-design/issues/7909
-      this.neverShown = !!column.fixed;
-    }
-  }
-
-  setSelectedKeys = ({ selectedKeys }: { selectedKeys: string[] }) => {
-    this.setState({ selectedKeys });
-  }
-
-  setVisible(visible: boolean) {
-    const { column } = this.props;
-    if (!('filterDropdownVisible' in column)) {
-      this.setState({ visible });
-    }
-    if (column.onFilterDropdownVisibleChange) {
-      column.onFilterDropdownVisibleChange(visible);
-    }
-  }
-
-  handleClearFilters = () => {
-    this.setState({
-      selectedKeys: [],
-    }, this.handleConfirm);
-  }
-
-  handleConfirm = () => {
-    this.setVisible(false);
-    this.confirmFilter();
-  }
-
-  onVisibleChange = (visible: boolean) => {
-    this.setVisible(visible);
-    if (!visible) {
-      this.confirmFilter();
-    }
-  }
-
-  confirmFilter() {
-    if (this.state.selectedKeys !== this.props.selectedKeys) {
-      this.props.confirmFilter(this.props.column, this.state.selectedKeys);
-    }
-  }
-
-  renderMenuItem(item: ColumnFilterItem) {
-    const { column } = this.props;
-    const multiple = ('filterMultiple' in column) ? column.filterMultiple : true;
-    const input = multiple ? (
-      <Checkbox checked={this.state.selectedKeys.indexOf(item.value.toString()) >= 0} />
-    ) : (
-      <Radio checked={this.state.selectedKeys.indexOf(item.value.toString()) >= 0} />
-    );
-
-    return (
-      <MenuItem key={item.value}>
-        {input}
-        <span>{item.text}</span>
-      </MenuItem>
-    );
-  }
-
-  hasSubMenu() {
-    const { column: { filters = [] } } = this.props;
-    return filters.some(item => !!(item.children && item.children.length > 0));
-  }
-
-  renderMenus(items: ColumnFilterItem[]): React.ReactElement<any>[] {
-    return items.map(item => {
-      if (item.children && item.children.length > 0) {
-        const { keyPathOfSelectedItem } = this.state;
-        const containSelected = Object.keys(keyPathOfSelectedItem).some(
-          key => keyPathOfSelectedItem[key].indexOf(item.value) >= 0,
-        );
-        const subMenuCls = containSelected ? `${this.props.dropdownPrefixCls}-submenu-contain-selected` : '';
-        return (
-          <SubMenu title={item.text} className={subMenuCls} key={item.value.toString()}>
-            {this.renderMenus(item.children)}
-          </SubMenu>
-        );
-      }
-      return this.renderMenuItem(item);
-    });
-  }
-
-  handleMenuItemClick = (info: { keyPath: string, key: string }) => {
-    if (info.keyPath.length <= 1) {
-      return;
-    }
-    const keyPathOfSelectedItem = this.state.keyPathOfSelectedItem;
-    if (this.state.selectedKeys.indexOf(info.key) >= 0) {
-      // deselect SubMenu child
-      delete keyPathOfSelectedItem[info.key];
-    } else {
-      // select SubMenu child
-      keyPathOfSelectedItem[info.key] = info.keyPath;
-    }
-    this.setState({ keyPathOfSelectedItem });
-  }
-
-  renderFilterIcon = () => {
-    const { column, locale, prefixCls } = this.props;
-    const filterIcon = column.filterIcon as any;
-    const dropdownSelectedClass = this.props.selectedKeys.length > 0 ? `${prefixCls}-selected` : '';
-
-    return filterIcon ? React.cloneElement(filterIcon as any, {
-      title: locale.filterTitle,
-      className: classNames(filterIcon.className, {
-        [`${prefixCls}-icon`]: true,
-      }),
-    }) : <Icon title={locale.filterTitle} type="filter" className={dropdownSelectedClass} />;
-  }
-  render() {
-    const { column, locale, prefixCls, dropdownPrefixCls, getPopupContainer } = this.props;
-    // default multiple selection in filter dropdown
-    const multiple = ('filterMultiple' in column) ? column.filterMultiple : true;
-    const dropdownMenuClass = classNames({
-      [`${dropdownPrefixCls}-menu-without-submenu`]: !this.hasSubMenu(),
-    });
-    const menus = column.filterDropdown ? (
-      <FilterDropdownMenuWrapper>
-        {column.filterDropdown}
-      </FilterDropdownMenuWrapper>
-    ) : (
-      <FilterDropdownMenuWrapper className={`${prefixCls}-dropdown`}>
-        <Menu
-          multiple={multiple}
-          onClick={this.handleMenuItemClick}
-          prefixCls={`${dropdownPrefixCls}-menu`}
-          className={dropdownMenuClass}
-          onSelect={this.setSelectedKeys}
-          onDeselect={this.setSelectedKeys}
-          selectedKeys={this.state.selectedKeys}
-        >
-          {this.renderMenus(column.filters!)}
-        </Menu>
-        <div className={`${prefixCls}-dropdown-btns`}>
-          <a
-            className={`${prefixCls}-dropdown-link confirm`}
-            onClick={this.handleConfirm}
-          >
-            {locale.filterConfirm}
-          </a>
-          <a
-            className={`${prefixCls}-dropdown-link clear`}
-            onClick={this.handleClearFilters}
-          >
-            {locale.filterReset}
-          </a>
-        </div>
-      </FilterDropdownMenuWrapper>
-    );
-
-    return (
-      <Dropdown
-        trigger={['click']}
-        overlay={menus}
-        visible={this.neverShown ? false : this.state.visible}
-        onVisibleChange={this.onVisibleChange}
-        getPopupContainer={getPopupContainer}
-        forceRender
-      >
-        {this.renderFilterIcon()}
-      </Dropdown>
-    );
-  }
-}
diff --git a/package-lock.json b/package-lock.json
index b2cdd9e77..e1b9b05d3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3424,6 +3424,14 @@
             "resolved": "https://registry.npm.taobao.org/dom-align/download/dom-align-1.6.7.tgz",
             "integrity": "sha1-aFgTjvtrd0Bc6ZFG0L5eT3KCgT8="
         },
+        "dom-closest": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/dom-closest/-/dom-closest-0.2.0.tgz",
+            "integrity": "sha1-69n5HRvyLo1vR3h2u80+yQIWwM8=",
+            "requires": {
+                "dom-matches": "2.0.0"
+            }
+        },
         "dom-converter": {
             "version": "0.1.4",
             "resolved": "http://r.cnpmjs.org/dom-converter/download/dom-converter-0.1.4.tgz",
@@ -3441,6 +3449,11 @@
                 }
             }
         },
+        "dom-matches": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/dom-matches/-/dom-matches-2.0.0.tgz",
+            "integrity": "sha1-0nKLQWqHUzmA6wibhI0lPPI6dYw="
+        },
         "dom-scroll-into-view": {
             "version": "1.2.1",
             "resolved": "https://registry.npm.taobao.org/dom-scroll-into-view/download/dom-scroll-into-view-1.2.1.tgz",
diff --git a/package.json b/package.json
index 80b0c5fae..81d5ce805 100644
--- a/package.json
+++ b/package.json
@@ -131,6 +131,7 @@
     "component-classes": "^1.2.6",
     "css-animation": "^1.4.1",
     "dom-align": "^1.6.7",
+    "dom-closest": "^0.2.0",
     "dom-scroll-into-view": "^1.2.1",
     "enquire.js": "^2.1.6",
     "eslint-plugin-vue": "^3.13.0",