tjz
7 years ago
20 changed files with 2966 additions and 12 deletions
@ -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> |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -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