add table
parent
507576b2f4
commit
a01905cc6e
|
@ -7,7 +7,7 @@ import LocaleReceiver from '../locale-provider/LocaleReceiver'
|
||||||
import { getOptionProps } from '../_util/props-util'
|
import { getOptionProps } from '../_util/props-util'
|
||||||
import VcPagination from '../vc-pagination'
|
import VcPagination from '../vc-pagination'
|
||||||
|
|
||||||
export const PaginationProps = {
|
export const PaginationProps = () => ({
|
||||||
total: PropTypes.number,
|
total: PropTypes.number,
|
||||||
defaultCurrent: PropTypes.number,
|
defaultCurrent: PropTypes.number,
|
||||||
current: PropTypes.number,
|
current: PropTypes.number,
|
||||||
|
@ -29,11 +29,11 @@ export const PaginationProps = {
|
||||||
prefixCls: PropTypes.string,
|
prefixCls: PropTypes.string,
|
||||||
selectPrefixCls: PropTypes.string,
|
selectPrefixCls: PropTypes.string,
|
||||||
itemRender: PropTypes.any,
|
itemRender: PropTypes.any,
|
||||||
}
|
})
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
...PaginationProps,
|
...PaginationProps(),
|
||||||
prefixCls: PropTypes.string.def('ant-pagination'),
|
prefixCls: PropTypes.string.def('ant-pagination'),
|
||||||
selectPrefixCls: PropTypes.string.def('ant-select'),
|
selectPrefixCls: PropTypes.string.def('ant-select'),
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,20 +2,27 @@
|
||||||
import PropTypes from '../_util/vue-types'
|
import PropTypes from '../_util/vue-types'
|
||||||
import BaseMixin from '../_util/BaseMixin'
|
import BaseMixin from '../_util/BaseMixin'
|
||||||
import isCssAnimationSupported from '../_util/isCssAnimationSupported'
|
import isCssAnimationSupported from '../_util/isCssAnimationSupported'
|
||||||
import { filterEmpty } from '../_util/props-util'
|
import { filterEmpty, initDefaultProps } from '../_util/props-util'
|
||||||
import getTransitionProps from '../_util/getTransitionProps'
|
import getTransitionProps from '../_util/getTransitionProps'
|
||||||
|
|
||||||
|
export const SpinProps = () => ({
|
||||||
|
prefixCls: PropTypes.string,
|
||||||
|
spinning: PropTypes.bool,
|
||||||
|
size: PropTypes.oneOf(['small', 'default', 'large']),
|
||||||
|
wrapperClassName: PropTypes.string,
|
||||||
|
tip: PropTypes.string,
|
||||||
|
delay: PropTypes.number,
|
||||||
|
})
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Spin',
|
name: 'Spin',
|
||||||
mixins: [BaseMixin],
|
mixins: [BaseMixin],
|
||||||
props: {
|
props: initDefaultProps(SpinProps(), {
|
||||||
prefixCls: PropTypes.string.def('ant-spin'),
|
prefixCls: 'ant-spin',
|
||||||
spinning: PropTypes.bool.def(true),
|
size: 'default',
|
||||||
size: PropTypes.oneOf(['small', 'default', 'large']).def('default'),
|
spinning: true,
|
||||||
wrapperClassName: PropTypes.string.def(''),
|
wrapperClassName: '',
|
||||||
tip: PropTypes.string,
|
}),
|
||||||
delay: PropTypes.number,
|
|
||||||
},
|
|
||||||
data () {
|
data () {
|
||||||
const { spinning } = this
|
const { spinning } = this
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
import Spin from './Spin'
|
import Spin from './Spin'
|
||||||
|
|
||||||
|
export { SpinProps } from './Spin'
|
||||||
export default Spin
|
export default Spin
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { ColumnProps } from './interface'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Column',
|
||||||
|
props: ColumnProps,
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
import PropTypes from '../_util/vue-types'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ColumnGroup',
|
||||||
|
props: {
|
||||||
|
title: PropTypes.any,
|
||||||
|
},
|
||||||
|
__ANT_TABLE_COLUMN_GROUP: true,
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
handelClick (e) {
|
||||||
|
this.$emit('click', e)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render () {
|
||||||
|
const { $slots, handelClick } = this
|
||||||
|
return (
|
||||||
|
<div onClick={handelClick}>
|
||||||
|
{$slots.default}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,59 @@
|
||||||
|
import PropTypes from '../_util/vue-types'
|
||||||
|
|
||||||
|
import { Store } from './createStore'
|
||||||
|
|
||||||
|
const BodyRowProps = {
|
||||||
|
store: Store,
|
||||||
|
rowKey: PropTypes.string,
|
||||||
|
prefixCls: PropTypes.string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function createTableRow (Component = 'tr') {
|
||||||
|
const BodyRow = {
|
||||||
|
name: 'BodyRow',
|
||||||
|
props: BodyRowProps,
|
||||||
|
data () {
|
||||||
|
const { selectedRowKeys } = this.store.getState()
|
||||||
|
|
||||||
|
return {
|
||||||
|
selected: selectedRowKeys.indexOf(this.rowKey) >= 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted () {
|
||||||
|
this.subscribe()
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy () {
|
||||||
|
if (this.unsubscribe) {
|
||||||
|
this.unsubscribe()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
subscribe () {
|
||||||
|
const { store, rowKey } = this
|
||||||
|
this.unsubscribe = store.subscribe(() => {
|
||||||
|
const { selectedRowKeys } = this.store.getState()
|
||||||
|
const selected = selectedRowKeys.indexOf(rowKey) >= 0
|
||||||
|
if (selected !== this.selected) {
|
||||||
|
this.selected = selected
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const className = {
|
||||||
|
[`${this.props.prefixCls}-row-selected`]: this.selected,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Component class={className}>
|
||||||
|
{this.$slots.default}
|
||||||
|
</Component>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return BodyRow
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import PropTypes from '../_util/vue-types'
|
||||||
|
export const Store = PropTypes.shape({
|
||||||
|
setState: PropTypes.func,
|
||||||
|
getState: PropTypes.func,
|
||||||
|
subscribe: PropTypes.func,
|
||||||
|
}).loose
|
||||||
|
|
||||||
|
import create from '../_util/store/create'
|
||||||
|
const createStore = create
|
||||||
|
|
||||||
|
export default createStore
|
|
@ -0,0 +1,228 @@
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,210 @@
|
||||||
|
---
|
||||||
|
category: Components
|
||||||
|
cols: 1
|
||||||
|
type: Data Display
|
||||||
|
title: Table
|
||||||
|
---
|
||||||
|
|
||||||
|
A table displays rows of data.
|
||||||
|
|
||||||
|
## When To Use
|
||||||
|
|
||||||
|
- To display a collection of structured data.
|
||||||
|
- To sort, search, paginate, filter data.
|
||||||
|
|
||||||
|
## How To Use
|
||||||
|
|
||||||
|
Specify `dataSource` of Table as an array of data.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const dataSource = [{
|
||||||
|
key: '1',
|
||||||
|
name: 'Mike',
|
||||||
|
age: 32,
|
||||||
|
address: '10 Downing Street'
|
||||||
|
}, {
|
||||||
|
key: '2',
|
||||||
|
name: 'John',
|
||||||
|
age: 42,
|
||||||
|
address: '10 Downing Street'
|
||||||
|
}];
|
||||||
|
|
||||||
|
const columns = [{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
}, {
|
||||||
|
title: 'Age',
|
||||||
|
dataIndex: 'age',
|
||||||
|
key: 'age',
|
||||||
|
}, {
|
||||||
|
title: 'Address',
|
||||||
|
dataIndex: 'address',
|
||||||
|
key: 'address',
|
||||||
|
}];
|
||||||
|
|
||||||
|
<Table dataSource={dataSource} columns={columns} />
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Table
|
||||||
|
|
||||||
|
| Property | Description | Type | Default |
|
||||||
|
| -------- | ----------- | ---- | ------- |
|
||||||
|
| bordered | Whether to show all table borders | boolean | `false` |
|
||||||
|
| columns | Columns of table | [ColumnProps](https://git.io/vMMXC)\[] | - |
|
||||||
|
| components | Override default table elements | object | - |
|
||||||
|
| dataSource | Data record array to be displayed | any\[] | - |
|
||||||
|
| defaultExpandAllRows | Expand all rows initially | boolean | `false` |
|
||||||
|
| defaultExpandedRowKeys | Initial expanded row keys | string\[] | - |
|
||||||
|
| expandedRowKeys | Current expanded row keys | string\[] | - |
|
||||||
|
| expandedRowRender | Expanded container render for each row | Function(record):ReactNode | - |
|
||||||
|
| expandRowByClick | Whether to expand row by clicking anywhere in the whole row | boolean | `false` |
|
||||||
|
| footer | Table footer renderer | Function(currentPageData) | |
|
||||||
|
| indentSize | Indent size in pixels of tree data | number | 15 |
|
||||||
|
| loading | Loading status of table | boolean\|[object](https://ant.design/components/spin-cn/#API) ([more](https://github.com/ant-design/ant-design/issues/4544#issuecomment-271533135)) | `false` |
|
||||||
|
| locale | i18n text including filter, sort, empty text, etc | object | filterConfirm: 'Ok' <br> filterReset: 'Reset' <br> emptyText: 'No Data' <br> [Default](https://github.com/ant-design/ant-design/issues/575#issuecomment-159169511) |
|
||||||
|
| pagination | Pagination [config](/components/pagination/), hide it by setting it to `false` | object | |
|
||||||
|
| rowClassName | Row's className | Function(record, index):string | - |
|
||||||
|
| rowKey | Row's unique key, could be a string or function that returns a string | string\|Function(record):string | `key` |
|
||||||
|
| rowSelection | Row selection [config](#rowSelection) | object | null |
|
||||||
|
| scroll | Whether table can be scrolled in x/y direction, `x` or `y` can be a number that indicates the width and height of table body | object | - |
|
||||||
|
| showHeader | Whether to show table header | boolean | `true` |
|
||||||
|
| size | Size of table | `default` \| `middle` \| `small` | `default` |
|
||||||
|
| title | Table title renderer | Function(currentPageData) | |
|
||||||
|
| onChange | Callback executed when pagination, filters or sorter is changed | Function(pagination, filters, sorter) | |
|
||||||
|
| onExpand | Callback executed when the row expand icon is clicked | Function(expanded, record) | |
|
||||||
|
| onExpandedRowsChange | Callback executed when the expanded rows change | Function(expandedRows) | |
|
||||||
|
| onHeaderRow | Set props on per header row | Function(column, index) | - |
|
||||||
|
| onRow | Set props on per row | Function(record, index) | - |
|
||||||
|
|
||||||
|
#### onRow usage
|
||||||
|
|
||||||
|
Same as `onRow` `onHeaderRow` `onCell` `onHeaderCell`
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<Table
|
||||||
|
onRow={(record) => {
|
||||||
|
return {
|
||||||
|
onClick: () => {}, // click row
|
||||||
|
onMouseEnter: () => {}, // mouse enter row
|
||||||
|
onXxxx...
|
||||||
|
};
|
||||||
|
)}
|
||||||
|
onHeaderRow={(column) => {
|
||||||
|
return {
|
||||||
|
onClick: () => {}, // click header row
|
||||||
|
};
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Column
|
||||||
|
|
||||||
|
One of the Table `columns` prop for describing the table's columns, Column has the same API.
|
||||||
|
|
||||||
|
| Property | Description | Type | Default |
|
||||||
|
| -------- | ----------- | ---- | ------- |
|
||||||
|
| className | className of this column | string | - |
|
||||||
|
| colSpan | Span of this column's title | number | |
|
||||||
|
| dataIndex | Display field of the data record, could be set like `a.b.c` | string | - |
|
||||||
|
| defaultSortOrder | Default order of sorted values: `'ascend'` `'descend'` `null` | string | - |
|
||||||
|
| filterDropdown | Customized filter overlay | ReactNode | - |
|
||||||
|
| filterDropdownVisible | Whether `filterDropdown` is visible | boolean | - |
|
||||||
|
| filtered | Whether the `dataSource` is filtered | boolean | `false` |
|
||||||
|
| filteredValue | Controlled filtered value, filter icon will highlight | string\[] | - |
|
||||||
|
| filterIcon | Customized filter icon | ReactNode | `false` |
|
||||||
|
| filterMultiple | Whether multiple filters can be selected | boolean | `true` |
|
||||||
|
| filters | Filter menu config | object\[] | - |
|
||||||
|
| fixed | Set column to be fixed: `true`(same as left) `'left'` `'right'` | boolean\|string | `false` |
|
||||||
|
| key | Unique key of this column, you can ignore this prop if you've set a unique `dataIndex` | string | - |
|
||||||
|
| render | Renderer of the table cell. The return value should be a ReactNode, or an object for [colSpan/rowSpan config](#components-table-demo-colspan-rowspan) | Function(text, record, index) {} | - |
|
||||||
|
| sorter | Sort function for local sort, see [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)'s compareFunction. If you need sort buttons only, set to `true` | Function\|boolean | - |
|
||||||
|
| sortOrder | Order of sorted values: `'ascend'` `'descend'` `false` | boolean\|string | - |
|
||||||
|
| title | Title of this column | string\|ReactNode | - |
|
||||||
|
| width | Width of this column | string\|number | - |
|
||||||
|
| onCell | Set props on per cell | Function(record) | - |
|
||||||
|
| onFilter | Callback executed when the confirm filter button is clicked | Function | - |
|
||||||
|
| onFilterDropdownVisibleChange | Callback executed when `filterDropdownVisible` is changed | function(visible) {} | - |
|
||||||
|
| onHeaderCell | Set props on per header cell | Function(column) | - |
|
||||||
|
|
||||||
|
### ColumnGroup
|
||||||
|
|
||||||
|
| Property | Description | Type | Default |
|
||||||
|
| -------- | ----------- | ---- | ------- |
|
||||||
|
| title | Title of the column group | string\|ReactNode | - |
|
||||||
|
|
||||||
|
### rowSelection
|
||||||
|
|
||||||
|
Properties for row selection.
|
||||||
|
|
||||||
|
| Property | Description | Type | Default |
|
||||||
|
| -------- | ----------- | ---- | ------- |
|
||||||
|
| fixed | Fixed selection column on the left | boolean | - |
|
||||||
|
| getCheckboxProps | Get Checkbox or Radio props | Function(record) | - |
|
||||||
|
| hideDefaultSelections | Remove the default `Select All` and `Select Invert` selections | boolean | `false` |
|
||||||
|
| selectedRowKeys | Controlled selected row keys | string\[] | \[] |
|
||||||
|
| selections | Custom selection [config](#rowSelection), only displays default selections when set to `true` | object\[]\|boolean | - |
|
||||||
|
| type | `checkbox` or `radio` | `checkbox` \| `radio` | `checkbox` |
|
||||||
|
| onChange | Callback executed when selected rows change | Function(selectedRowKeys, selectedRows) | - |
|
||||||
|
| onSelect | Callback executed when select/deselect one row | Function(record, selected, selectedRows) | - |
|
||||||
|
| onSelectAll | Callback executed when select/deselect all rows | Function(selected, selectedRows, changeRows) | - |
|
||||||
|
| onSelectInvert | Callback executed when row selection is inverted | Function(selectedRows) | - |
|
||||||
|
|
||||||
|
### selection
|
||||||
|
|
||||||
|
| Property | Description | Type | Default |
|
||||||
|
| -------- | ----------- | ---- | ------- |
|
||||||
|
| key | Unique key of this selection | string | - |
|
||||||
|
| text | Display text of this selection | string\|React.ReactNode | - |
|
||||||
|
| onSelect | Callback executed when this selection is clicked | Function(changeableRowKeys) | - |
|
||||||
|
|
||||||
|
## Using in TypeScript
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { Table } from 'antd';
|
||||||
|
import { ColumnProps } from 'antd/lib/table';
|
||||||
|
|
||||||
|
interface IUser {
|
||||||
|
key: number,
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: ColumnProps<IUser>[] = [{
|
||||||
|
key: 'name',
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
}];
|
||||||
|
|
||||||
|
const data: IUser[] = [{
|
||||||
|
key: 0,
|
||||||
|
name: 'Jack',
|
||||||
|
}];
|
||||||
|
|
||||||
|
class UserTable extends Table<IUser> {}
|
||||||
|
|
||||||
|
<UserTable columns={columns} dataSource={data} />
|
||||||
|
|
||||||
|
// Use JSX style API
|
||||||
|
class NameColumn extends Table.Column<IUser> {}
|
||||||
|
|
||||||
|
<UserTable dataSource={data}>
|
||||||
|
<NameColumn key="name" title="Name" dataIndex="name" />
|
||||||
|
</UserTable>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Note
|
||||||
|
|
||||||
|
According to [React documentation](https://facebook.github.io/react/docs/lists-and-keys.html#keys), every child in array should be assigned a unique key. The values inside `dataSource` and `columns` should follow this in Table, and `dataSource[i].key` would be treated as key value default for `dataSource`.
|
||||||
|
|
||||||
|
If `dataSource[i].key` is not provided, then you should specify the primary key of dataSource value via `rowKey`. If not, warnings like above will show in browser console.
|
||||||
|
|
||||||
|
![](https://os.alipayobjects.com/rmsportal/luLdLvhPOiRpyss.png)
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// primary key is uid
|
||||||
|
return <Table rowKey="uid" />;
|
||||||
|
// or
|
||||||
|
return <Table rowKey={record => record.uid} />;
|
||||||
|
```
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Table from './Table'
|
||||||
|
|
||||||
|
export * from './interface'
|
||||||
|
|
||||||
|
export default Table
|
|
@ -0,0 +1,210 @@
|
||||||
|
---
|
||||||
|
category: Components
|
||||||
|
cols: 1
|
||||||
|
type: Data Display
|
||||||
|
title: Table
|
||||||
|
subtitle: 表格
|
||||||
|
---
|
||||||
|
|
||||||
|
展示行列数据。
|
||||||
|
|
||||||
|
## 何时使用
|
||||||
|
|
||||||
|
- 当有大量结构化的数据需要展现时;
|
||||||
|
- 当需要对数据进行排序、搜索、分页、自定义操作等复杂行为时。
|
||||||
|
|
||||||
|
## 如何使用
|
||||||
|
|
||||||
|
指定表格的数据源 `dataSource` 为一个数组。
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const dataSource = [{
|
||||||
|
key: '1',
|
||||||
|
name: '胡彦斌',
|
||||||
|
age: 32,
|
||||||
|
address: '西湖区湖底公园1号'
|
||||||
|
}, {
|
||||||
|
key: '2',
|
||||||
|
name: '胡彦祖',
|
||||||
|
age: 42,
|
||||||
|
address: '西湖区湖底公园1号'
|
||||||
|
}];
|
||||||
|
|
||||||
|
const columns = [{
|
||||||
|
title: '姓名',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
}, {
|
||||||
|
title: '年龄',
|
||||||
|
dataIndex: 'age',
|
||||||
|
key: 'age',
|
||||||
|
}, {
|
||||||
|
title: '住址',
|
||||||
|
dataIndex: 'address',
|
||||||
|
key: 'address',
|
||||||
|
}];
|
||||||
|
|
||||||
|
<Table dataSource={dataSource} columns={columns} />
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### Table
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| bordered | 是否展示外边框和列边框 | boolean | false |
|
||||||
|
| columns | 表格列的配置描述,具体项见下表 | [ColumnProps](https://git.io/vMMXC)\[] | - |
|
||||||
|
| components | 覆盖默认的 table 元素 | object | - |
|
||||||
|
| dataSource | 数据数组 | any\[] | |
|
||||||
|
| defaultExpandAllRows | 初始时,是否展开所有行 | boolean | false |
|
||||||
|
| defaultExpandedRowKeys | 默认展开的行 | string\[] | - |
|
||||||
|
| expandedRowKeys | 展开的行,控制属性 | string\[] | - |
|
||||||
|
| expandedRowRender | 额外的展开行 | Function(record):ReactNode | - |
|
||||||
|
| expandRowByClick | 通过点击行来展开子行 | boolean | `false` |
|
||||||
|
| footer | 表格尾部 | Function(currentPageData) | |
|
||||||
|
| indentSize | 展示树形数据时,每层缩进的宽度,以 px 为单位 | number | 15 |
|
||||||
|
| loading | 页面是否加载中 | boolean\|[object](https://ant.design/components/spin-cn/#API) ([更多](https://github.com/ant-design/ant-design/issues/4544#issuecomment-271533135)) | false |
|
||||||
|
| locale | 默认文案设置,目前包括排序、过滤、空数据文案 | object | filterConfirm: '确定' <br> filterReset: '重置' <br> emptyText: '暂无数据' <br> [默认值](https://github.com/ant-design/ant-design/issues/575#issuecomment-159169511) |
|
||||||
|
| pagination | 分页器,配置项参考 [pagination](/components/pagination/),设为 false 时不展示和进行分页 | object | |
|
||||||
|
| rowClassName | 表格行的类名 | Function(record, index):string | - |
|
||||||
|
| rowKey | 表格行 key 的取值,可以是字符串或一个函数 | string\|Function(record):string | 'key' |
|
||||||
|
| rowSelection | 列表项是否可选择,[配置项](#rowSelection) | object | null |
|
||||||
|
| scroll | 横向或纵向支持滚动,也可用于指定滚动区域的宽高度:`{{ x: true, y: 300 }}` | object | - |
|
||||||
|
| showHeader | 是否显示表头 | boolean | true |
|
||||||
|
| size | 正常或迷你类型,`default` or `small` | string | default |
|
||||||
|
| title | 表格标题 | Function(currentPageData) | |
|
||||||
|
| onChange | 分页、排序、筛选变化时触发 | Function(pagination, filters, sorter) | |
|
||||||
|
| onExpand | 点击展开图标时触发 | Function(expanded, record) | |
|
||||||
|
| onExpandedRowsChange | 展开的行变化时触发 | Function(expandedRows) | |
|
||||||
|
| onHeaderRow | 设置头部行属性 | Function(column, index) | - |
|
||||||
|
| onRow | 设置行属性 | Function(record, index) | - |
|
||||||
|
|
||||||
|
|
||||||
|
#### onRow 用法
|
||||||
|
|
||||||
|
适用于 `onRow` `onHeaderRow` `onCell` `onHeaderCell`。
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<Table
|
||||||
|
onRow={(record) => {
|
||||||
|
return {
|
||||||
|
onClick: () => {}, // 点击行
|
||||||
|
onMouseEnter: () => {}, // 鼠标移入行
|
||||||
|
onXxxx...
|
||||||
|
};
|
||||||
|
)}
|
||||||
|
onHeaderRow={(column) => {
|
||||||
|
return {
|
||||||
|
onClick: () => {}, // 点击表头行
|
||||||
|
};
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Column
|
||||||
|
|
||||||
|
列描述数据对象,是 columns 中的一项,Column 使用相同的 API。
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| className | 列的 className | string | - |
|
||||||
|
| colSpan | 表头列合并,设置为 0 时,不渲染 | number | |
|
||||||
|
| dataIndex | 列数据在数据项中对应的 key,支持 `a.b.c` 的嵌套写法 | string | - |
|
||||||
|
| filterDropdown | 可以自定义筛选菜单,此函数只负责渲染图层,需要自行编写各种交互 | ReactNode | - |
|
||||||
|
| filterDropdownVisible | 用于控制自定义筛选菜单是否可见 | boolean | - |
|
||||||
|
| filtered | 标识数据是否经过过滤,筛选图标会高亮 | boolean | false |
|
||||||
|
| filteredValue | 筛选的受控属性,外界可用此控制列的筛选状态,值为已筛选的 value 数组 | string\[] | - |
|
||||||
|
| filterIcon | 自定义 fiter 图标。 | ReactNode | false |
|
||||||
|
| filterMultiple | 是否多选 | boolean | true |
|
||||||
|
| filters | 表头的筛选菜单项 | object\[] | - |
|
||||||
|
| fixed | 列是否固定,可选 `true`(等效于 left) `'left'` `'right'` | boolean\|string | false |
|
||||||
|
| key | React 需要的 key,如果已经设置了唯一的 `dataIndex`,可以忽略这个属性 | string | - |
|
||||||
|
| render | 生成复杂数据的渲染函数,参数分别为当前行的值,当前行数据,行索引,@return里面可以设置表格[行/列合并](#components-table-demo-colspan-rowspan) | Function(text, record, index) {} | - |
|
||||||
|
| sorter | 排序函数,本地排序使用一个函数(参考 [Array.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 的 compareFunction),需要服务端排序可设为 true | Function\|boolean | - |
|
||||||
|
| sortOrder | 排序的受控属性,外界可用此控制列的排序,可设置为 `'ascend'` `'descend'` `false` | boolean\|string | - |
|
||||||
|
| title | 列头显示文字 | string\|ReactNode | - |
|
||||||
|
| width | 列宽度 | string\|number | - |
|
||||||
|
| onCell | 设置单元格属性 | Function(record) | - |
|
||||||
|
| onFilter | 本地模式下,确定筛选的运行函数 | Function | - |
|
||||||
|
| onFilterDropdownVisibleChange | 自定义筛选菜单可见变化时调用 | function(visible) {} | - |
|
||||||
|
| onHeaderCell | 设置头部单元格属性 | Function(column) | - |
|
||||||
|
|
||||||
|
### ColumnGroup
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| title | 列头显示文字 | string\|ReactNode | - |
|
||||||
|
|
||||||
|
### rowSelection
|
||||||
|
|
||||||
|
选择功能的配置。
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| fixed | 把选择框列固定在左边 | boolean | - |
|
||||||
|
| getCheckboxProps | 选择框的默认属性配置 | Function(record) | - |
|
||||||
|
| hideDefaultSelections | 去掉『全选』『反选』两个默认选项 | boolean | false |
|
||||||
|
| selectedRowKeys | 指定选中项的 key 数组,需要和 onChange 进行配合 | string\[] | \[] |
|
||||||
|
| selections | 自定义选择项 [配置项](#selection), 设为 `true` 时使用默认选择项 | object\[]\|boolean | true |
|
||||||
|
| type | 多选/单选,`checkbox` or `radio` | string | `checkbox` |
|
||||||
|
| onChange | 选中项发生变化的时的回调 | Function(selectedRowKeys, selectedRows) | - |
|
||||||
|
| onSelect | 用户手动选择/取消选择某列的回调 | Function(record, selected, selectedRows) | - |
|
||||||
|
| onSelectAll | 用户手动选择/取消选择所有列的回调 | Function(selected, selectedRows, changeRows) | - |
|
||||||
|
| onSelectInvert | 用户手动选择反选的回调 | Function(selectedRows) | - |
|
||||||
|
|
||||||
|
### selection
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| key | React 需要的 key,建议设置 | string | - |
|
||||||
|
| text | 选择项显示的文字 | string\|React.ReactNode | - |
|
||||||
|
| onSelect | 选择项点击回调 | Function(changeableRowKeys) | - |
|
||||||
|
|
||||||
|
## 在 TypeScript 中使用
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { Table } from 'antd';
|
||||||
|
import { ColumnProps } from 'antd/lib/table';
|
||||||
|
|
||||||
|
interface IUser {
|
||||||
|
key: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: ColumnProps<IUser>[] = [{
|
||||||
|
key: 'name',
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
}];
|
||||||
|
|
||||||
|
const data: IUser[] = [{
|
||||||
|
key: 0,
|
||||||
|
name: 'Jack',
|
||||||
|
}];
|
||||||
|
|
||||||
|
class UserTable extends Table<IUser> {}
|
||||||
|
<UserTable columns={columns} dataSource={data} />
|
||||||
|
|
||||||
|
// 使用 JSX 风格的 API
|
||||||
|
class NameColumn extends Table.Column<IUser> {}
|
||||||
|
|
||||||
|
<UserTable dataSource={data}>
|
||||||
|
<NameColumn key="name" title="Name" dataIndex="name" />
|
||||||
|
</UserTable>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意
|
||||||
|
|
||||||
|
按照 [React 的规范](https://facebook.github.io/react/docs/lists-and-keys.html#keys),所有的组件数组必须绑定 key。在 Table 中,`dataSource` 和 `columns` 里的数据值都需要指定 `key` 值。对于 `dataSource` 默认将每列数据的 `key` 属性作为唯一的标识。
|
||||||
|
|
||||||
|
如果你的数据没有这个属性,务必使用 `rowKey` 来指定数据列的主键。若没有指定,控制台会出现以下的提示,表格组件也会出现各类奇怪的错误。
|
||||||
|
|
||||||
|
![](https://os.alipayobjects.com/rmsportal/luLdLvhPOiRpyss.png)
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// 比如你的数据主键是 uid
|
||||||
|
return <Table rowKey="uid" />;
|
||||||
|
// 或
|
||||||
|
return <Table rowKey={record => record.uid} />;
|
||||||
|
```
|
|
@ -0,0 +1,186 @@
|
||||||
|
import PropTypes from '../_util/vue-types'
|
||||||
|
import { PaginationProps as getPaginationProps } from '../pagination'
|
||||||
|
import { SpinProps as getSpinProps } from '../spin'
|
||||||
|
import { Store } from './createStore'
|
||||||
|
|
||||||
|
const PaginationProps = getPaginationProps()
|
||||||
|
const SpinProps = getSpinProps()
|
||||||
|
|
||||||
|
// export type CompareFn<T> = ((a: T, b: T) => number);
|
||||||
|
export const ColumnFilterItem = PropTypes.shape({
|
||||||
|
text: PropTypes.string,
|
||||||
|
value: PropTypes.string,
|
||||||
|
children: PropTypes.array,
|
||||||
|
}).loose
|
||||||
|
|
||||||
|
export const ColumnProps = {
|
||||||
|
title: PropTypes.any,
|
||||||
|
// key?: React.Key;
|
||||||
|
dataIndex: PropTypes.string,
|
||||||
|
render: PropTypes.func,
|
||||||
|
filters: PropTypes.arrayOf(ColumnFilterItem),
|
||||||
|
// onFilter: (value: any, record: T) => PropTypes.bool,
|
||||||
|
filterMultiple: PropTypes.bool,
|
||||||
|
filterDropdown: PropTypes.any,
|
||||||
|
filterDropdownVisible: PropTypes.bool,
|
||||||
|
// onFilterDropdownVisibleChange?: (visible: boolean) => void;
|
||||||
|
sorter: PropTypes.oneOfType([PropTypes.boolean, PropTypes.func]),
|
||||||
|
defaultSortOrder: PropTypes.oneOf(['ascend', 'descend']),
|
||||||
|
colSpan: PropTypes.number,
|
||||||
|
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
// className: string,
|
||||||
|
fixed: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['left', 'right'])]),
|
||||||
|
filterIcon: PropTypes.any,
|
||||||
|
filteredValue: PropTypes.array,
|
||||||
|
sortOrder: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['ascend', 'descend'])]),
|
||||||
|
// children?: ColumnProps<T>[];
|
||||||
|
// onCellClick?: (record: T, event: any) => void;
|
||||||
|
// onCell?: (record: T) => any;
|
||||||
|
// onHeaderCell?: (props: ColumnProps<T>) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// export interface TableComponents {
|
||||||
|
// table?: any;
|
||||||
|
// header?: {
|
||||||
|
// wrapper?: any;
|
||||||
|
// row?: any;
|
||||||
|
// cell?: any;
|
||||||
|
// };
|
||||||
|
// body?: {
|
||||||
|
// wrapper?: any;
|
||||||
|
// row?: any;
|
||||||
|
// cell?: any;
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
export const TableLocale = PropTypes.shape({
|
||||||
|
filterTitle: PropTypes.string,
|
||||||
|
filterConfirm: PropTypes.any,
|
||||||
|
filterReset: PropTypes.any,
|
||||||
|
emptyText: PropTypes.any,
|
||||||
|
selectAll: PropTypes.any,
|
||||||
|
selectInvert: PropTypes.any,
|
||||||
|
}).loose
|
||||||
|
|
||||||
|
export const RowSelectionType = PropTypes.oneOf(['checkbox', 'radio'])
|
||||||
|
// export type SelectionSelectFn<T> = (record: T, selected: boolean, selectedRows: Object[]) => any;
|
||||||
|
|
||||||
|
export const TableRowSelection = {
|
||||||
|
type: RowSelectionType,
|
||||||
|
selectedRowKeys: PropTypes.array,
|
||||||
|
// onChange?: (selectedRowKeys: string[] | number[], selectedRows: Object[]) => any;
|
||||||
|
getCheckboxProps: PropTypes.func,
|
||||||
|
// onSelect?: SelectionSelectFn<T>;
|
||||||
|
// onSelectAll?: (selected: boolean, selectedRows: Object[], changeRows: Object[]) => any;
|
||||||
|
// onSelectInvert?: (selectedRows: Object[]) => any;
|
||||||
|
selections: PropTypes.oneOfType([PropTypes.array, PropTypes.bool]),
|
||||||
|
hideDefaultSelections: PropTypes.bool,
|
||||||
|
fixed: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TableProps = {
|
||||||
|
prefixCls: PropTypes.string,
|
||||||
|
dropdownPrefixCls: PropTypes.string,
|
||||||
|
rowSelection: PropTypes.shape(TableRowSelection).loose,
|
||||||
|
pagination: PropTypes.oneOfType([PropTypes.shape(PaginationProps).loose, PropTypes.bool]),
|
||||||
|
size: PropTypes.oneOf(['default', 'middle', 'small']),
|
||||||
|
dataSource: PropTypes.array,
|
||||||
|
components: PropTypes.object,
|
||||||
|
columns: PropTypes.array,
|
||||||
|
rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||||
|
rowClassName: PropTypes.func,
|
||||||
|
expandedRowRender: PropTypes.any,
|
||||||
|
defaultExpandAllRows: PropTypes.bool,
|
||||||
|
defaultExpandedRowKeys: PropTypes.array,
|
||||||
|
expandedRowKeys: PropTypes.array,
|
||||||
|
expandIconAsCell: PropTypes.bool,
|
||||||
|
expandIconColumnIndex: PropTypes.number,
|
||||||
|
expandRowByClick: PropTypes.bool,
|
||||||
|
// onExpandedRowsChange?: (expandedRowKeys: string[] | number[]) => void;
|
||||||
|
// onExpand?: (expanded: boolean, record: T) => void;
|
||||||
|
// onChange?: (pagination: PaginationProps | boolean, filters: string[], sorter: Object) => any;
|
||||||
|
loading: PropTypes.oneOfType([PropTypes.shape(SpinProps).loose, PropTypes.bool]),
|
||||||
|
locale: PropTypes.object,
|
||||||
|
indentSize: PropTypes.number,
|
||||||
|
// onRowClick?: (record: T, index: number, event: Event) => any;
|
||||||
|
// onRow?: (record: T, index: number) => any;
|
||||||
|
useFixedHeader: PropTypes.bool,
|
||||||
|
bordered: PropTypes.bool,
|
||||||
|
showHeader: PropTypes.bool,
|
||||||
|
footer: PropTypes.any,
|
||||||
|
title: PropTypes.any,
|
||||||
|
scroll: PropTypes.object,
|
||||||
|
childrenColumnName: PropTypes.string,
|
||||||
|
bodyStyle: PropTypes.any,
|
||||||
|
// className?: PropTypes.string,
|
||||||
|
// style?: React.CSSProperties;
|
||||||
|
// children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// export interface TableStateFilters {
|
||||||
|
// [key: string]: string[];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export interface TableState<T> {
|
||||||
|
// pagination: PaginationProps;
|
||||||
|
// filters: TableStateFilters;
|
||||||
|
// sortColumn: ColumnProps<T> | null;
|
||||||
|
// sortOrder: PropTypes.string,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export type SelectionItemSelectFn = (key: string[]) => any;
|
||||||
|
|
||||||
|
// export interface SelectionItem {
|
||||||
|
// key: PropTypes.string,
|
||||||
|
// text: PropTypes.any,
|
||||||
|
// onSelect: SelectionItemSelectFn;
|
||||||
|
// }
|
||||||
|
|
||||||
|
export const SelectionCheckboxAllProps = {
|
||||||
|
store: Store,
|
||||||
|
locale: PropTypes.any,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
getCheckboxPropsByItem: PropTypes.func,
|
||||||
|
getRecordKey: PropTypes.func,
|
||||||
|
data: PropTypes.array,
|
||||||
|
prefixCls: PropTypes.string,
|
||||||
|
// onSelect: (key: string, index: number, selectFunc: any) => void;
|
||||||
|
hideDefaultSelections: PropTypes.bool,
|
||||||
|
selections: PropTypes.oneOfType([PropTypes.array, PropTypes.bool]),
|
||||||
|
getPopupContainer: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
// export interface SelectionCheckboxAllState {
|
||||||
|
// checked: PropTypes.bool,
|
||||||
|
// indeterminate: PropTypes.bool,
|
||||||
|
// }
|
||||||
|
|
||||||
|
export const SelectionBoxProps = {
|
||||||
|
store: Store,
|
||||||
|
type: RowSelectionType,
|
||||||
|
defaultSelection: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
rowIndex: PropTypes.string,
|
||||||
|
name: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
// onChange: React.ChangeEventHandler<HTMLInputElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// export interface SelectionBoxState {
|
||||||
|
// checked?: PropTypes.bool,
|
||||||
|
// }
|
||||||
|
|
||||||
|
export const FilterMenuProps = {
|
||||||
|
locale: TableLocale,
|
||||||
|
selectedKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
column: PropTypes.shape(ColumnProps),
|
||||||
|
confirmFilter: PropTypes.func,
|
||||||
|
prefixCls: PropTypes.string,
|
||||||
|
dropdownPrefixCls: PropTypes.string,
|
||||||
|
getPopupContainer: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
// export interface FilterMenuState {
|
||||||
|
// selectedKeys: string[];
|
||||||
|
// keyPathOfSelectedItem: { [key: string]: string };
|
||||||
|
// visible?: PropTypes.bool,
|
||||||
|
// }
|
|
@ -0,0 +1,9 @@
|
||||||
|
import '../../style/index.less'
|
||||||
|
import './index.less'
|
||||||
|
|
||||||
|
// style dependencies
|
||||||
|
import '../../radio/style'
|
||||||
|
import '../../checkbox/style'
|
||||||
|
import '../../dropdown/style'
|
||||||
|
import '../../spin/style'
|
||||||
|
import '../../pagination/style'
|
|
@ -0,0 +1,568 @@
|
||||||
|
@import "../../style/themes/default";
|
||||||
|
@import "../../style/mixins/index";
|
||||||
|
|
||||||
|
@table-prefix-cls: ~"@{ant-prefix}-table";
|
||||||
|
@table-header-icon-color: @text-color-secondary;
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-wrapper {
|
||||||
|
.clearfix;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls} {
|
||||||
|
.reset-component;
|
||||||
|
position: relative;
|
||||||
|
border-radius: @border-radius-base @border-radius-base 0 0;
|
||||||
|
|
||||||
|
&-body {
|
||||||
|
transition: opacity .3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: @border-radius-base @border-radius-base 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-thead > tr > th {
|
||||||
|
background: @table-header-bg;
|
||||||
|
transition: background .3s ease;
|
||||||
|
text-align: left;
|
||||||
|
color: @heading-color;
|
||||||
|
font-weight: 500;
|
||||||
|
border-bottom: @border-width-base @border-style-base @border-color-split;
|
||||||
|
|
||||||
|
&[colspan] {
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{iconfont-css-prefix}-filter,
|
||||||
|
.@{table-prefix-cls}-filter-icon {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: @font-size-base;
|
||||||
|
cursor: pointer;
|
||||||
|
color: @table-header-icon-color;
|
||||||
|
transition: all .3s;
|
||||||
|
width: 14px;
|
||||||
|
font-weight: normal;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-column-sorter + .@{iconfont-css-prefix}-filter {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-filter-selected.@{iconfont-css-prefix}-filter {
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/ant-design/ant-design/issues/8979
|
||||||
|
&.@{table-prefix-cls}-column-has-filters {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-tbody > tr > td {
|
||||||
|
border-bottom: @border-width-base @border-style-base @border-color-split;
|
||||||
|
transition: all .3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-thead > tr,
|
||||||
|
&-tbody > tr {
|
||||||
|
transition: all .3s;
|
||||||
|
&.@{table-prefix-cls}-row-hover,
|
||||||
|
&:hover {
|
||||||
|
& > td {
|
||||||
|
background: @table-row-hover-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-thead > tr:hover {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-footer {
|
||||||
|
padding: @table-padding-vertical @table-padding-horizontal;
|
||||||
|
background: @table-header-bg;
|
||||||
|
border-radius: 0 0 @border-radius-base @border-radius-base;
|
||||||
|
position: relative;
|
||||||
|
border-top: @border-width-base @border-style-base @border-color-split;
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
height: 1px;
|
||||||
|
background: @table-header-bg;
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.@{table-prefix-cls}-bordered &-footer {
|
||||||
|
border: @border-width-base @border-style-base @border-color-split;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
padding: @table-padding-vertical 0;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
border-radius: @border-radius-base @border-radius-base 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.@{table-prefix-cls}-bordered &-title {
|
||||||
|
border: @border-width-base @border-style-base @border-color-split;
|
||||||
|
padding-left: @table-padding-horizontal;
|
||||||
|
padding-right: @table-padding-horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title + &-content {
|
||||||
|
position: relative;
|
||||||
|
border-radius: @border-radius-base @border-radius-base 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
.@{table-prefix-cls}-bordered & {
|
||||||
|
&,
|
||||||
|
table {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/ant-design/ant-design/issues/4373
|
||||||
|
&-without-column-header &-title + &-content,
|
||||||
|
&-without-column-header table {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-tbody > tr.@{table-prefix-cls}-row-selected td {
|
||||||
|
background: @table-selected-row-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-thead > tr > th.@{table-prefix-cls}-column-sort {
|
||||||
|
background: @table-header-sort-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-thead > tr > th,
|
||||||
|
&-tbody > tr > td {
|
||||||
|
padding: @table-padding-vertical @table-padding-horizontal;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-thead > tr > th.@{table-prefix-cls}-selection-column-custom {
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-thead > tr > th.@{table-prefix-cls}-selection-column,
|
||||||
|
&-tbody > tr > td.@{table-prefix-cls}-selection-column {
|
||||||
|
text-align: center;
|
||||||
|
min-width: 62px;
|
||||||
|
width: 62px;
|
||||||
|
.@{ant-prefix}-radio-wrapper {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-expand-icon-th,
|
||||||
|
&-row-expand-icon-cell {
|
||||||
|
text-align: center;
|
||||||
|
min-width: 50px;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
background: @table-header-bg;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header table {
|
||||||
|
border-radius: @border-radius-base @border-radius-base 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-loading {
|
||||||
|
position: relative;
|
||||||
|
.@{table-prefix-cls}-body {
|
||||||
|
background: @component-background;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.@{table-prefix-cls}-spin-holder {
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
margin-left: -30px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.@{table-prefix-cls}-with-pagination {
|
||||||
|
margin-top: -20px;
|
||||||
|
}
|
||||||
|
.@{table-prefix-cls}-without-pagination {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-column-sorter {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 8px;
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: normal;
|
||||||
|
color: @table-header-icon-color;
|
||||||
|
|
||||||
|
&-up,
|
||||||
|
&-down {
|
||||||
|
line-height: 6px;
|
||||||
|
display: block;
|
||||||
|
width: 14px;
|
||||||
|
height: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
&:hover .@{iconfont-css-prefix} {
|
||||||
|
color: @primary-4;
|
||||||
|
}
|
||||||
|
&.on {
|
||||||
|
.@{iconfont-css-prefix}-caret-up,
|
||||||
|
.@{iconfont-css-prefix}-caret-down {
|
||||||
|
color: @primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: 30px;
|
||||||
|
width: 14px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-up:after {
|
||||||
|
bottom: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-down:after {
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{iconfont-css-prefix}-caret-up,
|
||||||
|
.@{iconfont-css-prefix}-caret-down {
|
||||||
|
.iconfont-size-under-12px(8px);
|
||||||
|
line-height: 4px;
|
||||||
|
height: 4px;
|
||||||
|
transition: all .3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-bordered {
|
||||||
|
.@{table-prefix-cls}-header > table,
|
||||||
|
.@{table-prefix-cls}-body > table,
|
||||||
|
.@{table-prefix-cls}-fixed-left table,
|
||||||
|
.@{table-prefix-cls}-fixed-right table {
|
||||||
|
border: @border-width-base @border-style-base @border-color-split;
|
||||||
|
border-right: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.@{table-prefix-cls}-empty {
|
||||||
|
.@{table-prefix-cls}-placeholder {
|
||||||
|
border-left: @border-width-base @border-style-base @border-color-split;
|
||||||
|
border-right: @border-width-base @border-style-base @border-color-split;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.@{table-prefix-cls}-fixed-header {
|
||||||
|
.@{table-prefix-cls}-header > table {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-body > table {
|
||||||
|
border-top: 0;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-body-inner > table {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-placeholder {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-thead > tr > th {
|
||||||
|
border-bottom: @border-width-base @border-style-base @border-color-split;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-thead > tr > th,
|
||||||
|
.@{table-prefix-cls}-tbody > tr > td {
|
||||||
|
border-right: @border-width-base @border-style-base @border-color-split;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-placeholder {
|
||||||
|
position: relative;
|
||||||
|
padding: @table-padding-vertical @table-padding-horizontal;
|
||||||
|
background: @component-background;
|
||||||
|
border-bottom: @border-width-base @border-style-base @border-color-split;
|
||||||
|
text-align: center;
|
||||||
|
font-size: @font-size-base;
|
||||||
|
color: @text-color-secondary;
|
||||||
|
z-index: 1;
|
||||||
|
.@{iconfont-css-prefix} {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-pagination.@{ant-prefix}-pagination {
|
||||||
|
margin: 16px 0;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-filter-dropdown {
|
||||||
|
min-width: 96px;
|
||||||
|
margin-left: -8px;
|
||||||
|
background: @component-background;
|
||||||
|
border-radius: @border-radius-base;
|
||||||
|
box-shadow: @box-shadow-base;
|
||||||
|
|
||||||
|
.@{ant-prefix}-dropdown-menu {
|
||||||
|
border: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: @border-radius-base @border-radius-base 0 0;
|
||||||
|
|
||||||
|
// https://github.com/ant-design/ant-design/issues/4916
|
||||||
|
&-without-submenu {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item > label + span {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-sub {
|
||||||
|
border-radius: @border-radius-base;
|
||||||
|
box-shadow: @box-shadow-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-dropdown-submenu-contain-selected {
|
||||||
|
.@{ant-prefix}-dropdown-menu-submenu-title:after {
|
||||||
|
color: @primary-color;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 0 0 2px @primary-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{ant-prefix}-dropdown-menu-item {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .@{ant-prefix}-dropdown-menu > .@{ant-prefix}-dropdown-menu-item:last-child,
|
||||||
|
> .@{ant-prefix}-dropdown-menu > .@{ant-prefix}-dropdown-menu-submenu:last-child .@{ant-prefix}-dropdown-menu-submenu-title {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-btns {
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 7px 8px;
|
||||||
|
border-top: @border-width-base @border-style-base @border-color-split;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-link {
|
||||||
|
color: @link-color;
|
||||||
|
&:hover {
|
||||||
|
color: @link-hover-color;
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
color: @link-active-color;
|
||||||
|
}
|
||||||
|
&.confirm {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
&.clear {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-selection {
|
||||||
|
&-select-all-custom {
|
||||||
|
margin-right: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{iconfont-css-prefix}-down {
|
||||||
|
color: @table-header-icon-color;
|
||||||
|
transition: all .3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-menu {
|
||||||
|
min-width: 96px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-left: -30px;
|
||||||
|
background: @component-background;
|
||||||
|
border-radius: @border-radius-base;
|
||||||
|
box-shadow: @box-shadow-base;
|
||||||
|
|
||||||
|
.@{ant-prefix}-action-down {
|
||||||
|
color: @table-header-icon-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-down {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
&:hover .@{iconfont-css-prefix}-down {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-row {
|
||||||
|
&-expand-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 14px;
|
||||||
|
border: @border-width-base @border-style-base @border-color-split;
|
||||||
|
user-select: none;
|
||||||
|
background: @component-background;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-expanded:after {
|
||||||
|
content: '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
&-collapsed:after {
|
||||||
|
content: '+';
|
||||||
|
}
|
||||||
|
|
||||||
|
&-spaced {
|
||||||
|
visibility: hidden;
|
||||||
|
&:after {
|
||||||
|
content: '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[class*="@{table-prefix-cls}-row-level-0"] .@{table-prefix-cls}-selection-column > span {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tr&-expanded-row {
|
||||||
|
&,
|
||||||
|
&:hover {
|
||||||
|
background: #fbfbfb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-row-indent + .@{table-prefix-cls}-row-expand-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-scroll {
|
||||||
|
overflow: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
table {
|
||||||
|
width: auto;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-body-inner {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-fixed-header > &-content > &-scroll > &-body {
|
||||||
|
position: relative;
|
||||||
|
background: @component-background;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-fixed-header &-body-inner {
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-fixed-header &-scroll &-header {
|
||||||
|
overflow: scroll;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
margin-bottom: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-fixed-left,
|
||||||
|
&-fixed-right {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: box-shadow .3s ease;
|
||||||
|
border-radius: 0;
|
||||||
|
table {
|
||||||
|
width: auto;
|
||||||
|
background: @component-background;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-fixed-header &-fixed-left &-body-outer &-fixed,
|
||||||
|
&-fixed-header &-fixed-right &-body-outer &-fixed {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-fixed-left {
|
||||||
|
left: 0;
|
||||||
|
box-shadow: 6px 0 6px -4px @shadow-color;
|
||||||
|
.@{table-prefix-cls}-header {
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
// hide scrollbar in left fixed columns
|
||||||
|
.@{table-prefix-cls}-body-inner {
|
||||||
|
margin-right: -20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
.@{table-prefix-cls}-fixed-header & .@{table-prefix-cls}-body-inner {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
&,
|
||||||
|
table {
|
||||||
|
border-radius: @border-radius-base 0 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-fixed-right {
|
||||||
|
right: 0;
|
||||||
|
box-shadow: -6px 0 6px -4px @shadow-color;
|
||||||
|
&,
|
||||||
|
table {
|
||||||
|
border-radius: 0 @border-radius-base 0 0;
|
||||||
|
}
|
||||||
|
// hide expand row content in right-fixed Table
|
||||||
|
// https://github.com/ant-design/ant-design/issues/1898
|
||||||
|
.@{table-prefix-cls}-expanded-row {
|
||||||
|
color: transparent;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&&-scroll-position-left &-fixed-left {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&&-scroll-position-right &-fixed-right {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@import './size';
|
|
@ -0,0 +1,109 @@
|
||||||
|
.@{table-prefix-cls}-middle {
|
||||||
|
> .@{table-prefix-cls}-title,
|
||||||
|
> .@{table-prefix-cls}-footer {
|
||||||
|
padding: @table-padding-vertical*3/4 @table-padding-horizontal/2;
|
||||||
|
}
|
||||||
|
> .@{table-prefix-cls}-content {
|
||||||
|
> .@{table-prefix-cls}-header > table,
|
||||||
|
> .@{table-prefix-cls}-body > table,
|
||||||
|
> .@{table-prefix-cls}-scroll > .@{table-prefix-cls}-header > table,
|
||||||
|
> .@{table-prefix-cls}-scroll > .@{table-prefix-cls}-body > table,
|
||||||
|
> .@{table-prefix-cls}-fixed-left > .@{table-prefix-cls}-header > table,
|
||||||
|
> .@{table-prefix-cls}-fixed-right > .@{table-prefix-cls}-header > table,
|
||||||
|
> .@{table-prefix-cls}-fixed-left > .@{table-prefix-cls}-body-outer > .@{table-prefix-cls}-body-inner > table,
|
||||||
|
> .@{table-prefix-cls}-fixed-right > .@{table-prefix-cls}-body-outer > .@{table-prefix-cls}-body-inner > table {
|
||||||
|
> .@{table-prefix-cls}-thead > tr > th,
|
||||||
|
> .@{table-prefix-cls}-tbody > tr > td {
|
||||||
|
padding: @table-padding-vertical*3/4 @table-padding-horizontal/2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-small {
|
||||||
|
border: @border-width-base @border-style-base @border-color-split;
|
||||||
|
border-radius: @border-radius-base;
|
||||||
|
|
||||||
|
> .@{table-prefix-cls}-title,
|
||||||
|
> .@{table-prefix-cls}-footer {
|
||||||
|
padding: @table-padding-vertical/2 @table-padding-horizontal/2;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .@{table-prefix-cls}-title {
|
||||||
|
border-bottom: @border-width-base @border-style-base @border-color-split;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .@{table-prefix-cls}-content {
|
||||||
|
> .@{table-prefix-cls}-header > table,
|
||||||
|
> .@{table-prefix-cls}-body > table,
|
||||||
|
> .@{table-prefix-cls}-scroll > .@{table-prefix-cls}-header > table,
|
||||||
|
> .@{table-prefix-cls}-scroll > .@{table-prefix-cls}-body > table,
|
||||||
|
> .@{table-prefix-cls}-fixed-left > .@{table-prefix-cls}-header > table,
|
||||||
|
> .@{table-prefix-cls}-fixed-right > .@{table-prefix-cls}-header > table,
|
||||||
|
> .@{table-prefix-cls}-fixed-left > .@{table-prefix-cls}-body-outer > .@{table-prefix-cls}-body-inner > table,
|
||||||
|
> .@{table-prefix-cls}-fixed-right > .@{table-prefix-cls}-body-outer > .@{table-prefix-cls}-body-inner > table {
|
||||||
|
border: 0;
|
||||||
|
padding: 0 @table-padding-horizontal/2;
|
||||||
|
> .@{table-prefix-cls}-thead > tr > th,
|
||||||
|
> .@{table-prefix-cls}-tbody > tr > td {
|
||||||
|
padding: @table-padding-vertical/2 @table-padding-horizontal/2;
|
||||||
|
}
|
||||||
|
> .@{table-prefix-cls}-thead > tr > th {
|
||||||
|
background: @component-background;
|
||||||
|
border-bottom: @border-width-base @border-style-base @border-color-split;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .@{table-prefix-cls}-scroll > .@{table-prefix-cls}-header > table,
|
||||||
|
> .@{table-prefix-cls}-scroll > .@{table-prefix-cls}-body > table,
|
||||||
|
> .@{table-prefix-cls}-fixed-left > .@{table-prefix-cls}-header > table,
|
||||||
|
> .@{table-prefix-cls}-fixed-right > .@{table-prefix-cls}-header > table,
|
||||||
|
> .@{table-prefix-cls}-fixed-left > .@{table-prefix-cls}-body-outer > .@{table-prefix-cls}-body-inner > table,
|
||||||
|
> .@{table-prefix-cls}-fixed-right > .@{table-prefix-cls}-body-outer > .@{table-prefix-cls}-body-inner > table {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-header {
|
||||||
|
background: @component-background;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-placeholder,
|
||||||
|
.@{table-prefix-cls}-row:last-child td {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.@{table-prefix-cls}-bordered {
|
||||||
|
border-right: 0;
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-title {
|
||||||
|
border: 0;
|
||||||
|
border-bottom: @border-width-base @border-style-base @border-color-split;
|
||||||
|
border-right: @border-width-base @border-style-base @border-color-split;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-content {
|
||||||
|
border-right: @border-width-base @border-style-base @border-color-split;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-footer {
|
||||||
|
border: 0;
|
||||||
|
border-top: @border-width-base @border-style-base @border-color-split;
|
||||||
|
border-right: @border-width-base @border-style-base @border-color-split;
|
||||||
|
&:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-placeholder {
|
||||||
|
border-left: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.@{table-prefix-cls}-thead > tr > th:last-child,
|
||||||
|
.@{table-prefix-cls}-tbody > tr > td:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
|
||||||
|
export function flatArray (data = [], childrenName = 'children') {
|
||||||
|
const result = []
|
||||||
|
const loop = (array) => {
|
||||||
|
array.forEach(item => {
|
||||||
|
if (item[childrenName]) {
|
||||||
|
const newItem = { ...item }
|
||||||
|
delete newItem[childrenName]
|
||||||
|
result.push(newItem)
|
||||||
|
if (item[childrenName].length > 0) {
|
||||||
|
loop(item[childrenName])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
loop(data)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export function treeMap (tree, mapper, childrenName = 'children') {
|
||||||
|
return tree.map((node, index) => {
|
||||||
|
const extra = {}
|
||||||
|
if (node[childrenName]) {
|
||||||
|
extra[childrenName] = treeMap(node[childrenName], mapper, childrenName)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...mapper(node, index),
|
||||||
|
...extra,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function flatFilter (tree, callback) {
|
||||||
|
return tree.reduce((acc, node) => {
|
||||||
|
if (callback(node)) {
|
||||||
|
acc.push(node)
|
||||||
|
}
|
||||||
|
if (node.children) {
|
||||||
|
const children = flatFilter(node.children, callback)
|
||||||
|
acc.push(...children)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
// export function normalizeColumns (elements) {
|
||||||
|
// const columns = []
|
||||||
|
// React.Children.forEach(elements, (element) => {
|
||||||
|
// if (!React.isValidElement(element)) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// const column = {
|
||||||
|
// ...element.props,
|
||||||
|
// }
|
||||||
|
// if (element.key) {
|
||||||
|
// column.key = element.key
|
||||||
|
// }
|
||||||
|
// if (element.type && element.type.__ANT_TABLE_COLUMN_GROUP) {
|
||||||
|
// column.children = normalizeColumns(column.children)
|
||||||
|
// }
|
||||||
|
// columns.push(column)
|
||||||
|
// })
|
||||||
|
// return columns
|
||||||
|
// }
|
Loading…
Reference in New Issue