hc-components
Version:
基于react的通用组件库
324 lines (302 loc) • 9.47 kB
JSX
import React from 'react';
import PropTypes from 'prop-types';
import Input from 'antd/lib/input';
import Tree from 'antd/lib/tree';
import Radio from 'antd/lib/radio';
import Icon from 'antd/lib/icon';
import Alert from 'antd/lib/alert';
import Spin from 'antd/lib/spin';
import './index.less';
export class Explorer extends React.PureComponent {
static propTypes = {
mode: PropTypes.string,
dataSource: PropTypes.array,
changeNodes: PropTypes.array,
noSearch: PropTypes.bool,
cateId: PropTypes.any,
value: PropTypes.any,
onSelect: PropTypes.func,
onChange: PropTypes.func,
searchPlaceholder: PropTypes.string,
menu: PropTypes.element,
title: PropTypes.string,
actions: PropTypes.array,
onSearch: PropTypes.func,
noResult: PropTypes.string,
pending: PropTypes.bool,
showLine: PropTypes.bool,
leafIcon: PropTypes.string,
byLeaf: PropTypes.bool
}
/**
* dataSource = [
* {
* id,
* children
* },
* ...
* ]
*/
static defaultProps = {
dataSource: [],
changeNodes: [],
// single, multiple
mode: 'single',
noResult: 'No Result!',
pending: false
}
constructor(props, context) {
super(props, context);
this.state = {
searchValue: '',
expandedKeys: [],
activeTab: props.cateId,
activeKey: props.value,
dataSource: props.changeNodes ? this.handleChange(props.dataSource, props.changeNodes) : props.dataSource
};
if (props.value) {
this.state.expandedKeys = this.getExpandedKeys(this.getData(), item => item.id === props.value);
}
this._lazySearch = null;
}
handleChange(dataSource, changeNodes, map) {
if (!changeNodes.length) return dataSource;
if (!map) {
map = {};
changeNodes.forEach((node, index) => {
map[node.id] = index;
});
}
const newDataSource = [];
dataSource.forEach(item => {
if (map[item.id] === undefined) {
if (item.children) {
item.children = this.handleChange(item.children, changeNodes, map);
}
} else {
changeNodes.splice(map[item.id], 1);
newDataSource.push(item);
}
});
return newDataSource;
}
componentWillReceiveProps(nextProps) {
if (nextProps.dataSource !== this.props.dataSource || nextProps.changeNodes !== this.props.changeNodes) {
this.setState({
dataSource: this.handleChange(nextProps.dataSource, nextProps.changeNodes)
});
}
}
getExpandedKeys(dataSource, matcher, parentKey) {
const expandedKeys = [];
dataSource.forEach(item => {
if (matcher(item)) {
// 因为有子集有命中的,所以父级ID要加进去
if (parentKey) {
expandedKeys.push(String(parentKey));
parentKey = null;
}
expandedKeys.push(String(item.id));
} else if (item.children && item.children.length) {
const keys = this.getExpandedKeys(item.children, matcher, item.id);
const len = keys.length;
if (len) {
if (item.id !== keys[len - 1]) {
expandedKeys.push(String(item.id));
}
for (let i = 0; i < len; i++) {
expandedKeys.push(String(keys[i]));
}
}
}
});
return expandedKeys;
}
getIcon(item) {
const icon = item.icon ? (<Icon type={item.icon} />) : null;
if (this.props.leafIcon) {
return icon || (item.children ? null : (<Icon type={this.props.leafIcon} />));
} else {
return icon;
}
}
loopTreeNode(dataSource, searchValue = '') {
const actions = this.props.actions;
const activeKey = this.state.activeKey;
let firstMatchId;
/**
* item = {
* id,
* name,
* icon,
* children
* }
*/
const nodes = dataSource.map((item) => {
let index = -1;
let beforeStr;
let afterStr;
let title;
let label = item.name;
if (item.title) {
label = item.title;
} else if (searchValue) {
index = label.indexOf(searchValue);
beforeStr = label.substr(0, index);
afterStr = label.substr(index + searchValue.length);
}
/**
* actions = [{
* icon,
* title,
* text,
* onAction
* }, ...]
*/
let btns = null;
if (actions && (this.props.byLeaf && !item.children || !this.props.byLeaf)) {
btns = (<span className="o-com-explorer_elem-btns">{
actions.map((btnItem, key) => {
return (<a
key={key}
href="javascript:;"
onClick={() => {
btnItem.onAction && btnItem.onAction(item);
}}
title={btnItem.title || btnItem.text}
>{btnItem.icon ? (<Icon type={btnItem.icon} />) : (btnItem.text)}</a>);
})}</span>);
}
if (index > -1) {
if (!firstMatchId) {
firstMatchId = item.id;
}
title = (
<span id={item.id} className="o-com-explorer-elem_span">
{btns}
<span onClick={() => this.handleSelect(item)}>
{this.getIcon(item)}
{beforeStr}
<span style={{color: '#f50'}}>{searchValue}</span>
{afterStr}
</span>
</span>
);
} else {
title = (<span id={item.id}>
{btns}
<span onClick={() => this.handleSelect(item)}>{this.getIcon(item)}{label}</span>
</span>);
}
if (item.children && item.children.length) {
return (
<Tree.TreeNode key={item.id} title={title} className={activeKey === item.id ? 'active' : ''}>
{this.loopTreeNode(item.children, searchValue)}
</Tree.TreeNode>
);
} else {
return <Tree.TreeNode key={item.id} title={title} className={activeKey === item.id ? 'active' : ''} />;
}
});
if (firstMatchId && this._lazySearch) {
this._lazySearch(firstMatchId);
this._lazySearch = null;
}
return nodes;
}
handleSelect(node) {
this.props.onSelect && this.props.onSelect(node);
this.setState({
expandedKeys: this.getExpandedKeys(this.getData(), item => node.id === item.id),
activeKey: node.id
});
}
getData() {
if (this.props.mode === 'multiple') {
return this.state.dataSource[this.state.activeTab || 0];
} else {
return this.state.dataSource;
}
}
renderTabs(dataSource) {
const {activeTab} = this.state;
return (<div className="o-com-explorer-elem_btn-group">
<Radio.Group value={activeTab} onChange={e => {
this.setState({
searchValue: '',
expandedKeys: [],
activeTab: e.target.value
});
this.props.onChange && this.props.onChange(e.target.value);
}}>
{dataSource.map(item => {
return (<Radio.Button value={item.id} key={item.id}>{item.name}</Radio.Button>);
})}
</Radio.Group>
</div>);
}
renderSearch(dataSource) {
const searchProps = {
size: 'small',
value: this.state.searchValue,
placeholder: this.props.searchPlaceholder,
onChange: (e) => {
const value = e.target.value;
this.setState({
searchValue: value
});
this.props.onSearch && this.props.onSearch(value);
clearTimeout(this._timer);
this._timer = setTimeout(() => {
const expandedKeys = this.getExpandedKeys(dataSource, item => item.name.indexOf(value) > -1);
this.setState({
expandedKeys: expandedKeys
}, () => {
this._lazySearch = (firstMatchId) => {
this._timer = setTimeout(() => {
const node = document.getElementById(firstMatchId).parentNode.parentNode.parentNode;
node.scrollIntoView();
}, 200);
};
});
}, 200);
}
};
return (<div className="o-com-explorer-elem_search"><Input.Search {...searchProps} /></div>);
}
render() {
const {menu, noSearch, title, mode} = this.props;
const {searchValue, activeKey, expandedKeys} = this.state;
const dataSource = this.getData();
const treeProps = {
checkStrictly: true,
expandedKeys: expandedKeys,
autoExpandParent: searchValue !== undefined,
onExpand: (expandedKeys) => {
this.setState({
expandedKeys: expandedKeys,
searchValue: ''
});
},
multiple: false,
selectable: false,
selectedKeys: activeKey ? [String(activeKey)] : [],
showLine: this.props.showLine
};
return (<div className="o-com-explorer">
<div className="o-com-explorer_wrapper">
{title ? (<div className="o-com-explorer_title">
<span className="o-com-explorer_link">{title}</span>
{menu}
</div>) : null}
{mode === 'multiple' ? this.renderTabs(dataSource) : null}
{noSearch ? null : this.renderSearch(dataSource)}
<div className="o-com-explorer_tree">
{dataSource.length ? (<Tree {...treeProps}>
{this.loopTreeNode(dataSource, searchValue)}
</Tree>) : (<Spin spinning={this.props.pending}><Alert style={{margin: '0 6px'}} message={this.props.pending ? 'Loading...' : this.props.noResult} type="warning" /></Spin>)}
</div>
</div>
</div>);
}
}