UNPKG

18.4 kBJavaScriptView Raw
1import React from 'react';
2import classNames from 'classnames';
3import List from './list';
4import Operation from './operation';
5import Search from './search';
6import PropTypes from 'prop-types';
7import { DragDropContext } from 'react-beautiful-dnd';
8import { reorder,move } from './utils';
9
10function noop() {}
11
12const defaultProps = {
13 dataSource: [],
14 render: noop,
15 showSearch: false,
16 searchPlaceholder: 'Search',
17 notFoundContent: 'Not Found',
18 showCheckbox: true,
19 draggable: false,
20 appendToBottom: false,
21 renderOperation:()=>'',//自定义操作
22};
23
24const propTypes = {
25 prefixCls: PropTypes.string,
26 dataSource: PropTypes.array,
27 render: PropTypes.func,
28 targetKeys: PropTypes.array,
29 onChange: PropTypes.func,
30 height: PropTypes.number,
31 listStyle: PropTypes.object,
32 className: PropTypes.string,
33 titles: PropTypes.array,
34 operations: PropTypes.array,
35 showSearch: PropTypes.bool,
36 filterOption: PropTypes.func,
37 searchPlaceholder: PropTypes.string,
38 notFoundContent: PropTypes.node,
39 body: PropTypes.func,
40 footer: PropTypes.func,
41 rowKey: PropTypes.func,
42 lazy: PropTypes.object,
43 showCheckbox: PropTypes.bool,
44 draggable: PropTypes.bool,
45 appendToBottom: PropTypes.bool,
46 renderOperation:PropTypes.func
47};
48
49const defaultTitles = ['', ''];
50class Transfer extends React.Component{
51
52 constructor(props) {
53 super(props);
54 const { selectedKeys = [], targetKeys = [] } = props;
55 this.state = {
56 leftFilter: '',
57 rightFilter: '',
58 sourceSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) === -1),
59 targetSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) > -1),
60 leftDataSource: [],
61 rightDataSource: [],
62 droppableId: '',
63 draggingItemId: ''
64 };
65 this.cacheTargetKeys = [...targetKeys];
66 }
67 componentDidMount(){
68 this.splitDataSource();
69 }
70
71 componentWillReceiveProps(nextProps) {
72 const { sourceSelectedKeys, targetSelectedKeys } = this.state;
73 if (nextProps.targetKeys !== this.props.targetKeys ||
74 nextProps.dataSource !== this.props.dataSource ||
75 nextProps.targetKeys !== this.cacheTargetKeys) {
76 // clear cached splited dataSource
77 this.splitedDataSource = null;
78
79 const { dataSource, targetKeys = [] } = nextProps;
80 function existInDateSourcekey(key) {
81 return dataSource.filter(item => item.key === key).length;
82 }
83 // clear key nolonger existed
84 // clear checkedKeys according to targetKeys
85 this.setState({
86 sourceSelectedKeys: sourceSelectedKeys.filter(existInDateSourcekey)
87 .filter(data => targetKeys.filter(key => key === data).length === 0),
88 targetSelectedKeys: targetSelectedKeys.filter(existInDateSourcekey)
89 .filter(data => targetKeys.filter(key => key === data).length > 0),
90 });
91 //异步加载时 || 动态改变targetKeys时
92 if(this.props.dataSource.length === 0 || !this.props.draggable){
93 this.splitDataSource(targetKeys,dataSource);
94 }
95 }
96 if (nextProps.selectedKeys) {
97 const targetKeys = nextProps.targetKeys;
98 this.setState({
99 sourceSelectedKeys: nextProps.selectedKeys.filter(key => targetKeys.indexOf(key) === -1),
100 targetSelectedKeys: nextProps.selectedKeys.filter(key => targetKeys.indexOf(key) > -1),
101 });
102 }
103 }
104
105 /**
106 * 给dataSource里的数据值指定唯一 key 值
107 */
108 addUniqueKey = (dataSource) => {
109 const { rowKey } = this.props;
110 if (rowKey) {
111 dataSource.forEach(record => {
112 record.key = rowKey(record);
113 });
114 }
115 return dataSource;
116 }
117
118 /**
119 * 从源dataSource中分离出leftDataSource和rightDataSource(点击按钮穿梭时调用)
120 * @param {*} newTargetKeys 更新后的targetKeys
121 * @param {*} newDataSource 异步加载数据源时,从nextProps中获取的dataSource
122 */
123 splitDataSource(newTargetKeys, newDataSource) {
124 // targetKeys:展示在右边列表的数据集
125 if (this.splitedDataSource) {
126 return this.splitedDataSource;
127 }
128
129 let targetKeys = newTargetKeys || this.props.targetKeys;
130 //异步加载数据源时/移除已选时
131 let dataSource = newDataSource || this.props.dataSource;
132
133 dataSource = this.addUniqueKey(dataSource);
134 this.allSourceKeys = dataSource.map(({key}) => key);
135
136 const leftDataSource = dataSource.filter(({ key }) => targetKeys.indexOf(key) === -1);
137 // const rightDataSource = dataSource.filter(({key}) => targetKeys.indexOf(key) > -1);
138 // 右侧数据源根据传入的targetKeys进行排序
139 let rightDataSource = [];
140 let tempIndex = -1;
141 targetKeys.forEach((key) => {
142 tempIndex = this.allSourceKeys.indexOf(key);
143 rightDataSource.push(dataSource[tempIndex]);
144 })
145
146 this.splitedDataSource = {
147 leftDataSource,
148 rightDataSource,
149 };
150 this.setState({
151 leftDataSource,
152 rightDataSource,
153 })
154
155 return this.splitedDataSource;
156 }
157
158 /**
159 * 从自定义顺序的dataSource中分离出leftDataSource和rightDataSource(拖拽场景调用)
160 * @param {*} newTargetKeys 更新后的targetKeys
161 * @param {*} newDataSource 通过 leftDataSource.concat(rightDataSource) 得到的newDataSource
162 */
163 splitDataSource2(newTargetKeys, newDataSource) {
164 // targetKeys:展示在右边列表的数据集
165 if (this.splitedDataSource) {
166 return this.splitedDataSource;
167 }
168
169 let targetKeys = newTargetKeys || this.props.targetKeys;
170 //异步加载数据源时/移除已选时
171 let sourceDataSource = this.props.dataSource;
172 newDataSource = this.addUniqueKey(newDataSource);
173 sourceDataSource = this.addUniqueKey(sourceDataSource);
174 const leftDataSource = sourceDataSource.filter(({ key }) => targetKeys.indexOf(key) === -1);
175 const rightDataSource = targetKeys.map(key => {
176 return newDataSource.find(data => data.key === key)
177 })
178 this.splitedDataSource = {
179 leftDataSource,
180 rightDataSource,
181 };
182 this.setState({
183 leftDataSource,
184 rightDataSource,
185 })
186
187 return this.splitedDataSource;
188 }
189
190 moveTo = (direction, insertIndex) => {
191 const { targetKeys = [], onChange, appendToBottom } = this.props;
192 const { sourceSelectedKeys, targetSelectedKeys, leftDataSource, rightDataSource, droppableId } = this.state;
193 const moveKeys = direction === 'right' ? sourceSelectedKeys : targetSelectedKeys;
194 // let temp = appendToBottom ? targetKeys.concat(moveKeys) : moveKeys.concat(targetKeys); // 在这里
195 let temp = []
196 if (appendToBottom) {
197 temp = targetKeys.concat(moveKeys)
198 } else if (insertIndex) {
199 targetKeys.splice(insertIndex, 0, ...moveKeys)
200 temp = targetKeys
201 } else {
202 temp = moveKeys.concat(targetKeys);
203 }
204 // move items to target box
205 const newTargetKeys = direction === 'right'
206 ? temp
207 : targetKeys.filter(targetKey => moveKeys.indexOf(targetKey) === -1);
208
209 // empty checked keys
210 const oppositeDirection = direction === 'right' ? 'left' : 'right';
211 this.setState({
212 [this.getSelectedKeysName(oppositeDirection)]: [],
213 });
214 this.handleSelectChange(oppositeDirection, []);
215
216 if (onChange) {
217 onChange(newTargetKeys, direction, moveKeys);
218 }
219 // 区分拖拽穿梭还是点击穿梭
220 let newDataSource = leftDataSource.concat(rightDataSource);
221 droppableId ? this.splitDataSource2(newTargetKeys,newDataSource) : this.splitDataSource(newTargetKeys);
222 }
223
224 moveToLeft = () => this.moveTo('left')
225 moveToRight = insertIndex => this.moveTo('right', insertIndex)
226
227 /**
228 * List中的item选中/未选中状态改变时触发
229 * @param {*} direction 'left' or 'right'
230 * @param {*} holder 更新后的'sourceSelectedKeys' or 'targetSelectedKeys'
231 */
232 handleSelectChange(direction, holder) {
233 // onSelectChange:当选中的item发生改变时的回调 参数(sourceSelectedKeys, targetSelectedKeys)
234 const { sourceSelectedKeys, targetSelectedKeys } = this.state;
235 const onSelectChange = this.props.onSelectChange;
236 if (!onSelectChange) {
237 return;
238 }
239
240 if (direction === 'left') {
241 onSelectChange(holder, targetSelectedKeys);
242 } else {
243 onSelectChange(sourceSelectedKeys, holder);
244 }
245 }
246
247 handleSelectAll = (direction, filteredDataSource, checkAll) => {
248 const holder = checkAll ? [] : filteredDataSource.map(item => item.key);
249 this.handleSelectChange(direction, holder);
250
251 if (!this.props.selectedKeys) {
252 this.setState({
253 [this.getSelectedKeysName(direction)]: holder,
254 });
255 }
256 }
257
258 /**
259 * 左侧列表全选事件
260 * @param filteredDataSource dataSource中刨去设置为disabled的部分
261 * @param checkAll 是否是全选状态 true:全选
262 */
263 handleLeftSelectAll = (filteredDataSource, checkAll) => {
264 this.handleSelectAll('left', filteredDataSource, checkAll)
265 }
266 handleRightSelectAll = (filteredDataSource, checkAll) => (
267 this.handleSelectAll('right', filteredDataSource, checkAll)
268 )
269
270 /**
271 * 搜索框值更改事件
272 * @param direction 'left' or 'right'
273 * @param value 输入的值
274 */
275 handleFilter = (direction, value) => {
276 this.setState({
277 // add filter
278 [`${direction}Filter`]: value,
279 });
280 }
281
282 handleLeftFilter = (v) => this.handleFilter('left', v)
283 handleRightFilter = (v) => this.handleFilter('right', v)
284
285 /**
286 * 清空搜索框内容
287 * @param direction 'left' or 'right'
288 */
289 handleClear = (direction) => {
290 this.setState({
291 [`${direction}Filter`]: '',
292 });
293 }
294
295 handleLeftClear = () => this.handleClear('left')
296 handleRightClear = () => this.handleClear('right')
297
298 /**
299 * 点击list item,选中或取消选中
300 * @param direction 'left' or 'right'
301 * @param selectedItem 选中的item的信息,和dataSource数据源中的item信息一致
302 * @param checked 是否已勾选,true:已勾选 false:未勾选
303 */
304 handleSelect = (direction, selectedItem, checked) => {
305 const { sourceSelectedKeys, targetSelectedKeys } = this.state;
306 const holder = direction === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys];
307 const index = holder.indexOf(selectedItem.key);
308 if (checked) { //已勾选
309 holder.splice(index, 1);
310 }else if(index === -1){ //未勾选
311 holder.push(selectedItem.key);
312 }
313 this.handleSelectChange(direction, holder);
314
315 if (!this.props.selectedKeys) {
316 this.setState({
317 [this.getSelectedKeysName(direction)]: holder,
318 });
319 }
320 }
321
322 handleLeftSelect = (selectedItem, checked) => this.handleSelect('left', selectedItem, checked);
323 handleRightSelect = (selectedItem, checked) => this.handleSelect('right', selectedItem, checked);
324
325 getTitles = () => {
326 if (this.props.titles) {
327 return this.props.titles;
328 }
329 if (this.context &&
330 this.context.antLocale &&
331 this.context.antLocale.Transfer
332 ) {
333 return this.context.antLocale.Transfer.titles || [];
334 }
335 return defaultTitles;
336 }
337
338 getSelectedKeysName(direction) {
339 return direction === 'left' ? 'sourceSelectedKeys' : 'targetSelectedKeys';
340 }
341
342 id2List = {
343 droppable_1: 'leftDataSource',
344 droppable_2: 'rightDataSource'
345 };
346
347 getList = id => this.state[this.id2List[id]];
348
349 /**
350 * 拖拽结束时触发
351 */
352 onDragEnd = result => {
353 this.setState({
354 draggingItemId: ''
355 })
356 const { source, destination,draggableId } = result;
357 let { targetKeys, onChange } = this.props;
358 let sourceIndex = source ? source.index : ''; //初始位置
359 let disIndex = destination ? destination.index : ''; //移动后的位置
360 let temp; //拖拽的元素
361
362 // case1:拖拽到列表之外
363 if (!destination) {
364 return;
365 }
366
367 if (destination.droppableId === 'droppable_1') {
368 // case2:在左侧列表中拖拽
369 if(source.droppableId === destination.droppableId) return;
370 // case3:从右往左拖拽(移除已选)
371 this.moveToLeft();
372 return;
373 }
374
375 // case4:在右侧列表中拖拽改变items顺序
376 if (source.droppableId === destination.droppableId) {
377 const items = reorder(
378 this.getList(source.droppableId),
379 targetKeys,
380 sourceIndex,
381 disIndex
382 );
383 this.setState({
384 rightDataSource: items.dataArr,
385 sourceSelectedKeys: [],
386 targetSelectedKeys: []
387 });
388 if (onChange) {
389 onChange(items.targetKeyArr, "", draggableId);
390 }
391 } else { // case5:从左往右拖拽(添加已选)
392 if (this.state.sourceSelectedKeys.length > 1) {
393 return this.moveToRight(destination.index)
394 }
395 const result = move( // 一次移动的方法
396 this.getList(source.droppableId),
397 this.getList(destination.droppableId),
398 source,
399 destination,
400 targetKeys
401 )
402 if (onChange) { // onChange事件
403 onChange(result.newTargetKeys, "", draggableId);
404 }
405 this.setState({
406 leftDataSource: result.droppable_1,
407 rightDataSource: result.droppable_2,
408 sourceSelectedKeys: [],
409 targetSelectedKeys: []
410 })
411 }
412 };
413
414 /**
415 * 拖拽开始时触发
416 */
417 onDragStart = result => {
418 let selectedItem = {};
419 const { source } = result;
420 selectedItem.key = result.draggableId;
421 if(source.droppableId === 'droppable_1'){ // leftMenu
422 this.handleLeftSelect(selectedItem);
423 }else if(source.droppableId === 'droppable_2'){ // rightMenu
424 this.handleRightSelect(selectedItem);
425 }
426 this.setState({
427 droppableId : source.droppableId,
428 draggingItemId: result.draggableId
429 })
430 }
431
432 render() {
433 const {
434 prefixCls = 'u-transfer', operations = [], showSearch, notFoundContent,
435 searchPlaceholder, body, footer, listStyle, className = '',
436 filterOption, render, lazy, showCheckbox, draggable,renderOperation
437 } = this.props;
438 const { leftFilter, rightFilter, sourceSelectedKeys, targetSelectedKeys, leftDataSource, rightDataSource, droppableId, draggingItemId } = this.state;
439
440 // const { leftDataSource, rightDataSource } = this.splitDataSource(this.props);
441 const leftActive = targetSelectedKeys.length > 0;
442 const rightActive = sourceSelectedKeys.length > 0;
443
444 const cls = classNames(className, prefixCls);
445
446 const titles = this.getTitles();
447 return (
448 <div className={cls}>
449 <DragDropContext onDragEnd={this.onDragEnd} onDragStart={this.onDragStart} >
450 <List
451 titleText={titles[0]} //左侧标题
452 dataSource={leftDataSource} //左侧数据源
453 filter={leftFilter} //搜索框中输入的内容
454 filterOption={filterOption} //搜索过滤方法 参数(inputValue, option)
455 style={listStyle} //自定义的columns的样式表
456 checkedKeys={sourceSelectedKeys} //左侧已勾选的item的keys
457 handleFilter={this.handleLeftFilter} //左侧搜索框值更改事件
458 handleClear={this.handleLeftClear} //清空左侧搜索框内容
459 handleSelect={this.handleLeftSelect} //点击左侧列表中的item,改变选中或取消选中状态
460 handleSelectAll={this.handleLeftSelectAll} //点击左侧全选
461 render={render}
462 showSearch={showSearch} //是否显示搜索框
463 searchPlaceholder={searchPlaceholder} //搜索框placeholder
464 notFoundContent={notFoundContent} //当没有相关内容的显示内容
465 body={body}
466 footer={footer}
467 prefixCls={`${prefixCls}-list`}
468 lazy={lazy}
469 showCheckbox={showCheckbox}
470 draggable={draggable}
471 id={'1'}
472 droppableId={droppableId}
473 draggingItemId={draggingItemId}
474 />
475 {!draggable?
476 <Operation
477 rightActive={rightActive}
478 rightArrowText={operations[0]}
479 moveToRight={this.moveToRight}
480 leftActive={leftActive}
481 leftArrowText={operations[1]}
482 moveToLeft={this.moveToLeft}
483 className={`${prefixCls}-operation`}
484 renderOperation={renderOperation}
485 />
486 : ''
487 }
488 <List
489 titleText={titles[1]} //右侧标题
490 dataSource={rightDataSource} //右侧数据源
491 filter={rightFilter} //搜索框中输入的内容
492 filterOption={filterOption} //搜索过滤方法 参数(inputValue, option)
493 style={listStyle} //自定义的columns的样式表
494 checkedKeys={targetSelectedKeys} //右侧已勾选的item的keys
495 handleFilter={this.handleRightFilter} //右侧搜索框值更改事件
496 handleClear={this.handleRightClear} //清空右侧搜索框内容
497 handleSelect={this.handleRightSelect} //点击右侧列表中的item,改变选中或取消选中状态
498 handleSelectAll={this.handleRightSelectAll} //点击右侧全选
499 render={render}
500 showSearch={showSearch} //是否显示搜索框
501 searchPlaceholder={searchPlaceholder} //搜索框placeholder
502 notFoundContent={notFoundContent} //当没有相关内容的显示内容
503 body={body}
504 footer={footer}
505 prefixCls={`${prefixCls}-list`}
506 lazy={lazy}
507 showCheckbox={showCheckbox}
508 draggable={draggable}
509 id={'2'}
510 />
511 </DragDropContext>
512 </div>
513 );
514 }
515}
516
517Transfer.List = Transfer.List;
518Transfer.Operation = Transfer.Operation;
519Transfer.Search = Transfer.Search;
520
521Transfer.propTypes = propTypes;
522Transfer.defaultProps = defaultProps;
523
524export default Transfer;