UNPKG

antui-admin

Version:
393 lines (363 loc) 13.1 kB
import React, {PropTypes} from 'react'; import classNames from 'classnames'; import { Button, Checkbox, Input, Tree, Spin } from 'antd'; import TreeTitle from './treeTitle'; const Search = Input.Search; const TreeNode = Tree.TreeNode; /** * 双栏穿梭选择框 其中,左边一栏为Tree */ export default class AsyncTreetransfer extends React.Component { constructor(props) { super(props); const { treeData, listData, treeLeafKeys, treeSelectLeaf, treeSelectLeafKeys } = this._init(props); this.state = { treeData, listData, treeLeafKeys, treeSelectLeaf, treeSelectLeafKeys, expandedKeys: [], listCheckNodes: [], autoExpandParent: true, treeLoading: false, searchValue: "", searching: false, }; } static propTypes = { /** 标题集合 */ title: PropTypes.array, /** 数据源,其中的数据将会被渲染到左侧框(Tree)中 */ dataSource: PropTypes.array, /** 显示在右侧框数据的key集合 */ targetKeys: PropTypes.array, /** 左侧框树叶子节点的长度 */ leafCount: PropTypes.number, /** 选项在左栏向右栏转移或者右栏数据更改时的回调函数 */ onChange: PropTypes.func, /** 异步数据加载与查询 */ onLoadTree: PropTypes.func, /** 是否显示搜索框 */ showSearch: PropTypes.bool, /** 搜索框的默认值 */ searchPlaceholder: PropTypes.string, /** 指定数据列的key */ rowKey: PropTypes.string, /** 指定数据列的title */ rowTitle: PropTypes.string, /** 指定数据列的children */ rowChildren: PropTypes.string, }; static defaultProps = { title: ['源数据', '目的数据'], dataSource: [], targetKeys: [], leafCount: 0, showSearch: false, searchPlaceholder: '请输入搜索内容', rowKey: 'key', rowTitle: 'title', rowChildren: 'children', } // init _init = ({ dataSource, targetKeys, rowKey, rowTitle }) => { // get leaf keys const listKeys = targetKeys.map(({key}) => key); const treeLeafKeys = []; const treeSelectLeaf = []; const treeSelectLeafKeys = []; const loop = data => data.map((item) => { const { [this.props.rowChildren]: children, [this.props.rowKey]: key } = item; if (children === undefined) { treeLeafKeys.push(key); if (listKeys.indexOf(key) > -1) { treeSelectLeaf.push(item); treeSelectLeafKeys.push(key); } } else { loop(item.children); } }); loop(dataSource); return { treeData: dataSource, listData: targetKeys, treeLeafKeys, treeSelectLeaf, treeSelectLeafKeys }; } componentWillReceiveProps(nextProps) { const { treeData, listData, treeLeafKeys, treeSelectLeaf, treeSelectLeafKeys } = this._init(nextProps); this.setState({ treeData, listData, treeLeafKeys, treeSelectLeaf, treeSelectLeafKeys }); } // tree init _generateTreeNodes = (treeData) => { const { searching, searchValue } = this.state; const expandedKeys = []; const loop = data => data.map((item) => { const { [this.props.rowChildren]: children, [this.props.rowKey]: key, [this.props.rowTitle]: title, ...others } = item; if (children === undefined) { let _title = <span>{title}</span>; // search value higlight if (searchValue !== "" && title.indexOf(searchValue) > -1) { const index = title.indexOf(searchValue); expandedKeys.push(key); _title = ( <span> {title.substr(0, index)} <span style={{ color: '#f50' }}>{searchValue}</span> {title.substr(index + searchValue.length)} </span> ); } return <TreeNode key={key} name={title} title={_title} isLeaf {...others} />; } else { const treeTitleProps = { title, onSelect: (e) => this._parentNodeSelect(e, key, children), onCanel: (e) => this._parentNodeCanel(e, key, children), }; return ( <TreeNode key={key} title={<TreeTitle {...treeTitleProps} />} {...others}> {loop(item.children)} </TreeNode> ); } }); const treeNodes = loop(treeData); const treeProps = { checkable: false, multiple: true, onSelect: this._handleTreeSelect, selectedKeys: this.state.treeSelectLeafKeys, loadData: this._treeLoadData, expandedKeys: searching ? expandedKeys : this.state.expandedKeys, autoExpandParent: searchValue !== "" ? true : this.state.autoExpandParent, onExpand: (keys) => { // console.log(keys); this.setState({ expandedKeys: keys, autoExpandParent: false, }); } }; return ( <Tree {...treeProps}> {treeNodes} </Tree> ); } // _handleTreeSelect = (selectedKeys, { selectedNodes }) => { // console.log(selectedNodes); const treeSelectLeafKeys = selectedKeys.filter((_k) => this.state.treeLeafKeys.indexOf(_k) > -1); const treeSelectLeaf = selectedNodes.map(({key, props}) => { const { name: title, isLeaf } = props; if (isLeaf) { return {key, title}; } }).filter((_v) => _v !== undefined); // console.log(treeSelectLeaf); this.setState({ treeSelectLeafKeys, treeSelectLeaf }); } _getLeafNodes = (dt) => { const _keys = []; const _nodes = []; // console.log(dt); const loop = data => data.forEach((item) => { const { [this.props.rowChildren]: children, [this.props.rowKey]: key } = item; if (children === undefined) { _keys.push(key); _nodes.push(item); } else { loop(item.children); } }); loop([dt]); return {_keys, _nodes}; } // select all children nodes _parentNodeSelect = (e, key, children) => { e.preventDefault(); e.stopPropagation(); new Promise((resolve) => { this.props.onLoadTree && this.props.onLoadTree({type: '_load', value: key, resolve}); }).then(() => { const { _keys, _nodes } = this._getLeafNodes({key, children}); const { treeSelectLeaf, treeSelectLeafKeys } = this.state; this.setState({ treeSelectLeaf: [..._nodes.filter(({key}) => treeSelectLeafKeys.indexOf(key) === -1), ...treeSelectLeaf], treeSelectLeafKeys: [...treeSelectLeafKeys, ..._keys] }); }); } // canel select all childr nen nodes _parentNodeCanel = (e, key, children) => { e.preventDefault(); e.stopPropagation(); const { _keys } = this._getLeafNodes({key, children}); const { treeSelectLeaf, treeSelectLeafKeys } = this.state; this.setState({ treeSelectLeaf: treeSelectLeaf.filter(({key}) => _keys.indexOf(key) === -1), treeSelectLeafKeys: treeSelectLeafKeys.filter((_k) => _keys.indexOf(_k) === -1) }); } // list checkbox click _handleListCheck = (e, {key, title}) => { if (e.target.checked) { this.setState({ listCheckNodes: [...this.state.listCheckNodes, {key, title}] }); } else { this.setState({ listCheckNodes: this.state.listCheckNodes.filter(({key: _k}) => _k !== key) }); } } // list checkbox all click _handleListCheckAll = (e) => { if (e.target.checked) { this.setState({ listCheckNodes: this.props.targetKeys }); } else { this.setState({ listCheckNodes: [], }); } } // _handleTreeToList _handleTreeToList = () => { const { treeSelectLeaf, treeSelectLeafKeys } = this.state; const { targetKeys, onChange } = this.props; onChange && onChange([...targetKeys.filter(({key}) => treeSelectLeafKeys.indexOf(key) === -1), ...treeSelectLeaf]); } // list delete _handleListToTree = () => { // delete tree clicked const { treeSelectLeafKeys } = this.state; this.setState({ listCheckNodes: [] }); this.props.onChange && this.props.onChange(this.props.targetKeys.filter(({key}) => treeSelectLeafKeys.indexOf(key) === -1)); } // list search _handleListSearch = (searchValue) => { if (searchValue === "") { const { listData } = this._init(this.props); this.setState({ listData }); } else { this.setState({ listData: this.state.listData.filter(({title}) => title.indexOf(searchValue) > -1) }); } } // tree search _handleTreeSearch = (searchValue) => { const { treeData } = this._init(this.props); // no search if (searchValue === "") { this.setState({ searchValue: "", treeData, autoExpandParent: false }); } else { // load tree search if (this.props.onLoadTree) { new Promise((resolve) => { this.setState({ treeLoading: true, searching: true }); this.props.onLoadTree && this.props.onLoadTree({type: '_search', value: searchValue, resolve}); }).then(() => { this.setState({ treeLoading: false }); }); } this.setState({ searchValue }); } } // tree load data _treeLoadData = (treeNode) => { return new Promise((resolve) => { this.props.onLoadTree && this.props.onLoadTree({type: '_expand', value: treeNode, resolve}); }); } render() { const { title, targetKeys, leafCount, showSearch, searchPlaceholder } = this.props; const { treeLoading, treeData, listData, treeSelectLeaf, listCheckNodes } = this.state; const listBodyStyle = classNames({ "antui-treetransfer-list-body": true, "antui-treetransfer-list-body-has-search": showSearch }); return ( <div className="antui-treetransfer"> <div className="antui-treetransfer-list antui-treetransfer-left"> <div className="antui-treetransfer-list-header"> <span className="antui-treetransfer-list-header-select"> { treeSelectLeaf.length === 0 ? leafCount : `${treeSelectLeaf.length}/${leafCount}` } 条数据 </span> <span className="antui-treetransfer-list-header-title"><span>{ title[0] }</span></span> </div> <div className={listBodyStyle}> { showSearch ? ( <div className="antui-treetransfer-list-body-search"> <Search placeholder={searchPlaceholder} onSearch={this._handleTreeSearch} /> </div> ) : null } <div className="antui-treetransfer-list-body-content"> { treeLoading ? <div className="antui-treetransfer-list-loading"><Spin /></div> :this._generateTreeNodes(treeData) } </div> </div> </div> <div className="antui-treetransfer-operation"> <Button type="primary" icon="right" size="small" disabled={treeSelectLeaf.length === 0} onClick={this._handleTreeToList} /> <Button type="primary" icon="left" size="small" disabled={listCheckNodes.length === 0} onClick={this._handleListToTree} /> </div> <div className="antui-treetransfer-list antui-treetransfer-right"> <div className="antui-treetransfer-list-header"> <Checkbox onClick={this._handleListCheckAll} indeterminate={listCheckNodes.length > 0 && listCheckNodes.length < targetKeys.length} checked={listCheckNodes.length > 0 && listCheckNodes.length === targetKeys.length} /> <span className="antui-treetransfer-list-header-select"> { listCheckNodes.length === 0 ? targetKeys.length : `${listCheckNodes.length}/${targetKeys.length}` } 条数据 </span> <span className="antui-treetransfer-list-header-title"><span>{ title[1] }</span></span> </div> <div className={listBodyStyle}> { showSearch ? ( <div className="antui-treetransfer-list-body-search"> <Search placeholder={searchPlaceholder} onSearch={this._handleListSearch} /> </div> ) : null } <ul className="antui-treetransfer-list-body-content"> { listData.map(({ key, title }) => ( <li key={key} className="list"> <Checkbox onClick={(e) => this._handleListCheck(e, {key, title})} checked={listCheckNodes.filter(({key: _k}) => _k === key).length > 0} /> <span>{title}</span> </li> )) } </ul> </div> </div> </div> ); } }