Compare commits

...

9 Commits

Author SHA1 Message Date
yangchch6 a74db774cd 异步加载问题 2019-04-21 23:14:18 +08:00
yangchch6 259d69d0cb 移除已选样式 2019-04-20 13:27:07 +08:00
yangchch6 dc24d15c96 去掉无用代码 2019-04-19 10:28:31 +08:00
yangchch6 c409179a58 拖拽 2019-04-15 17:29:51 +08:00
yangchch6 1f97ec83d6 增加删除已选操作区 2019-04-15 15:58:06 +08:00
yangchch6 8ec2d4633d 增加draggable参数,设置是否可以通过拖拽进行穿梭和排序 2019-04-13 16:29:29 +08:00
yangchch6 a710536dfe 拖拽改变顺序 2019-04-13 15:37:00 +08:00
yangchch6 98e355409f 增加showCheckbox参数 2019-04-11 18:24:27 +08:00
yangchch6 b2cac976f0 可拖拽的穿梭框 2019-04-11 15:45:44 +08:00
28 changed files with 16892 additions and 5267 deletions

190
README.md
View File

@ -1,94 +1,96 @@
# bee-transfer
[![npm version](https://img.shields.io/npm/v/bee-transfer.svg)](https://www.npmjs.com/package/bee-transfer)
[![Build Status](https://img.shields.io/travis/tinper-bee/bee-transfer/master.svg)](https://travis-ci.org/tinper-bee/bee-transfer)
[![Coverage Status](https://coveralls.io/repos/github/tinper-bee/bee-transfer/badge.svg?branch=master)](https://coveralls.io/github/tinper-bee/bee-transfer?branch=master)
[![devDependency Status](https://img.shields.io/david/dev/tinper-bee/bee-transfer.svg)](https://david-dm.org/tinper-bee/bee-transfer#info=devDependencies)
[![NPM downloads](http://img.shields.io/npm/dm/bee-transfer.svg?style=flat)](https://npmjs.org/package/bee-transfer)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tinper-bee/bee-transfer.svg)](http://isitmaintained.com/project/tinper-bee/bee-transfer "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/tinper-bee/bee-transfer.svg)](http://isitmaintained.com/project/tinper-bee/bee-transfer "Percentage of issues still open")
两框之间的元素迁移,非常直观且有效。一个或多个元素选择后点击方向按钮转到另一列框中。左栏是“源”,右边是“目标”
## 使用
### 使用单独的transfer包
#### 组件引入
先进行下载bee-transfer包
```
npm install --save bee-transfer
```
组件调用
```js
import Transfer from 'bee-transfer';
const mockData = [];
for (let i = 0; i < 20; i++) {
mockData.push({
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
disabled: i % 3 < 1,
});
}
ReactDOM.render(
<Transfer
dataSource={mockData}
titles={['Source', 'Target']}
render={item => item.title}
/>
, document.getElementById('target'));
```
#### 样式引入
- 可以使用link引入build目录下Transfer.css
```
<link rel="stylesheet" href="./node_modules/bee-transfer/build/Transfer.css">
```
- 可以在js中import样式
```js
import "./node_modules/bee-transfer/src/Transfer.scss"
//或是
import "./node_modules/bee-transfer/build/Transfer.css"
```
## API
|参数|说明|类型|默认值|
|:--|:---:|:--:|---:|
|dataSource|设置数据源。当有targetKey props存在时dataSource的数据刨去targetKey数据,剩下的都放在左边列表|[]|[]|
|render|自定义的展示出来的item,需要展示哪些字段|Function(record)|-|
|targetKeys|展示在右边列表的数据集|[]|[]|
|selectedKeys|所有选中的item的keys|[]|[]|
|onChange|当item在穿梭成功后的回调 参数(targetKeys, direction, moveKeys)|func|-|
|onSelectChange| 当选中的item发生改变时的回调 参数(sourceSelectedKeys, targetSelectedKeys)|fun|-|
|onScroll| 当滑动可选的item列表的回调 参数(direction, event)|func|-|
|listStyle|自定义的columns的样式表|object |-|
|className|class|string|''|''|
|titles|两columns的title|[]|-|
|operations|自定义按钮操作|[]|'>', '<'|
|showSearch|是否显示搜索框|boolean |false|
|filterOption|搜索过滤方法 参数(inputValue, option)|func或者boolean |-|
|searchPlaceholder|搜索框的默认显示文字|string|'Search here'|
|notFoundContent|当没有相关内容的显示内容|string或ReactNode| 'The list is empty'|
|footer|渲染底部的dom|ReactNode|-|
|lazy|懒加载dom|object|当tranfer放在bee-modal里 添加参数 lazy={{container:"modal"}}|
|onSearchChange|当搜索域变化的回调函数 参数(direction: 'left'|'right', event: Event)|func|-|
#### 开发调试
```sh
$ npm install -g bee-tools
$ git clone https://github.com/tinper-bee/bee-transfer
$ cd bee-transfer
$ npm install
$ npm run dev
```
# bee-transfer
[![npm version](https://img.shields.io/npm/v/bee-transfer.svg)](https://www.npmjs.com/package/bee-transfer)
[![Build Status](https://img.shields.io/travis/tinper-bee/bee-transfer/master.svg)](https://travis-ci.org/tinper-bee/bee-transfer)
[![Coverage Status](https://coveralls.io/repos/github/tinper-bee/bee-transfer/badge.svg?branch=master)](https://coveralls.io/github/tinper-bee/bee-transfer?branch=master)
[![devDependency Status](https://img.shields.io/david/dev/tinper-bee/bee-transfer.svg)](https://david-dm.org/tinper-bee/bee-transfer#info=devDependencies)
[![NPM downloads](http://img.shields.io/npm/dm/bee-transfer.svg?style=flat)](https://npmjs.org/package/bee-transfer)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tinper-bee/bee-transfer.svg)](http://isitmaintained.com/project/tinper-bee/bee-transfer "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/tinper-bee/bee-transfer.svg)](http://isitmaintained.com/project/tinper-bee/bee-transfer "Percentage of issues still open")
两框之间的元素迁移,非常直观且有效。一个或多个元素选择后点击方向按钮转到另一列框中。左栏是“源”,右边是“目标”
## 使用
### 使用单独的transfer包
#### 组件引入
先进行下载bee-transfer包
```
npm install --save bee-transfer
```
组件调用
```js
import Transfer from 'bee-transfer';
const mockData = [];
for (let i = 0; i < 20; i++) {
mockData.push({
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
disabled: i % 3 < 1,
});
}
ReactDOM.render(
<Transfer
dataSource={mockData}
titles={['Source', 'Target']}
render={item => item.title}
/>
, document.getElementById('target'));
```
#### 样式引入
- 可以使用link引入build目录下Transfer.css
```
<link rel="stylesheet" href="./node_modules/bee-transfer/build/Transfer.css">
```
- 可以在js中import样式
```js
import "./node_modules/bee-transfer/src/Transfer.scss"
//或是
import "./node_modules/bee-transfer/build/Transfer.css"
```
## API
|参数|说明|类型|默认值|
|:--|:---:|:--:|---:|
|dataSource|设置数据源。当有targetKey props存在时dataSource的数据刨去targetKey数据,剩下的都放在左边列表|[]|[]|
|render|自定义的展示出来的item,需要展示哪些字段|Function(record)|-|
|targetKeys|展示在右边列表的数据集|[]|[]|
|selectedKeys|所有选中的item的keys|[]|[]|
|onChange|当item在穿梭成功后的回调 参数(targetKeys, direction, moveKeys)|func|-|
|onSelectChange| 当选中的item发生改变时的回调 参数(sourceSelectedKeys, targetSelectedKeys)|fun|-|
|onScroll| 当滑动可选的item列表的回调 参数(direction, event)|func|-|
|listStyle|自定义的columns的样式表|object |-|
|className|class|string|''|''|
|titles|两columns的title|[]|-|
|operations|自定义按钮操作|[]|'>', '<'|
|showSearch|是否显示搜索框|boolean |false|
|filterOption|搜索过滤方法 参数(inputValue, option)|func或者boolean |-|
|searchPlaceholder|搜索框的默认显示文字|string|'Search'|
|notFoundContent|当没有相关内容的显示内容|string或ReactNode| 'Not Found'|
|footer|渲染底部的dom|ReactNode|-|
|lazy|懒加载dom|object|当tranfer放在bee-modal里 添加参数 lazy={{container:"modal"}}|
|onSearchChange|当搜索域变化的回调函数 参数(direction: 'left'|'right', event: Event)|func|-|
|showCheckbox|是否显示Checkbox复选框|bool|true|
|draggable|是否可以通过拖拽进行穿梭和排序|bool|false|
#### 开发调试
```sh
$ npm install -g bee-tools
$ git clone https://github.com/tinper-bee/bee-transfer
$ cd bee-transfer
$ npm install
$ npm run dev
```

View File

@ -1,14 +1,14 @@
'use strict';
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactComponentWithPureRenderMixin
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactComponentWithPureRenderMixin
*/
var shallowEqual = require('shallowequal');
@ -17,31 +17,31 @@ function shallowCompare(instance, nextProps, nextState) {
return !shallowEqual(instance.props, nextProps) || !shallowEqual(instance.state, nextState);
}
/**
* If your React component's render function is "pure", e.g. it will render the
* same result given the same props and state, provide this mixin for a
* considerable performance boost.
*
* Most React components have pure render functions.
*
* Example:
*
* var ReactComponentWithPureRenderMixin =
* require('ReactComponentWithPureRenderMixin');
* React.createClass({
* mixins: [ReactComponentWithPureRenderMixin],
*
* render: function() {
* return <div className={this.props.className}>foo</div>;
* }
* });
*
* Note: This only checks shallow equality for props and state. If these contain
* complex data structures this mixin may have false-negatives for deeper
* differences. Only mixin to components which have simple props and state, or
* use `forceUpdate()` when you know deep data structures have changed.
*
* See https://facebook.github.io/react/docs/pure-render-mixin.html
/**
* If your React component's render function is "pure", e.g. it will render the
* same result given the same props and state, provide this mixin for a
* considerable performance boost.
*
* Most React components have pure render functions.
*
* Example:
*
* var ReactComponentWithPureRenderMixin =
* require('ReactComponentWithPureRenderMixin');
* React.createClass({
* mixins: [ReactComponentWithPureRenderMixin],
*
* render: function() {
* return <div className={this.props.className}>foo</div>;
* }
* });
*
* Note: This only checks shallow equality for props and state. If these contain
* complex data structures this mixin may have false-negatives for deeper
* differences. Only mixin to components which have simple props and state, or
* use `forceUpdate()` when you know deep data structures have changed.
*
* See https://facebook.github.io/react/docs/pure-render-mixin.html
*/
var ReactComponentWithPureRenderMixin = {
shouldComponentUpdate: function shouldComponentUpdate(nextProps, nextState) {

File diff suppressed because it is too large Load Diff

View File

@ -28,14 +28,18 @@ var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
var _reactBeautifulDnd = require('react-beautiful-dnd');
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
var _utils = require('./utils');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _defaults(obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
@ -47,7 +51,11 @@ function noop() {}
var defaultProps = {
dataSource: [],
render: noop,
showSearch: false
showSearch: false,
searchPlaceholder: 'Search',
notFoundContent: 'Not Found',
showCheckbox: true,
draggable: false
};
var propTypes = {
@ -68,7 +76,9 @@ var propTypes = {
body: _propTypes2["default"].func,
footer: _propTypes2["default"].func,
rowKey: _propTypes2["default"].func,
lazy: _propTypes2["default"].object
lazy: _propTypes2["default"].object,
showCheckbox: _propTypes2["default"].bool,
draggable: _propTypes2["default"].bool
};
var defaultTitles = ['', ''];
@ -96,17 +106,24 @@ var Transfer = function (_React$Component) {
}),
targetSelectedKeys: selectedKeys.filter(function (key) {
return targetKeys.indexOf(key) > -1;
})
}),
leftDataSource: [],
rightDataSource: []
};
_this.cacheTargetKeys = [].concat(_toConsumableArray(targetKeys));
return _this;
}
Transfer.prototype.componentDidMount = function componentDidMount() {
this.splitDataSource();
};
Transfer.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
var _state = this.state,
sourceSelectedKeys = _state.sourceSelectedKeys,
targetSelectedKeys = _state.targetSelectedKeys;
if (nextProps.targetKeys !== this.props.targetKeys || nextProps.dataSource !== this.props.dataSource) {
if (nextProps.targetKeys !== this.props.targetKeys || nextProps.dataSource !== this.props.dataSource || nextProps.targetKeys !== this.cacheTargetKeys) {
var existInDateSourcekey = function existInDateSourcekey(key) {
return dataSource.filter(function (item) {
return item.key === key;
@ -134,6 +151,7 @@ var Transfer = function (_React$Component) {
}).length > 0;
})
});
this.splitDataSource(targetKeys, dataSource);
}
if (nextProps.selectedKeys) {
var _targetKeys = nextProps.targetKeys;
@ -147,18 +165,24 @@ var Transfer = function (_React$Component) {
});
}
};
/**
* 从dataSource中分离出leftDataSource和rightDataSource
* @param {*} newTargetKeys 更新后的targetKeys
* @param {*} newDataSource 异步加载数据源时从nextProps中获取的dataSource
*/
Transfer.prototype.splitDataSource = function splitDataSource() {
Transfer.prototype.splitDataSource = function splitDataSource(newTargetKeys, newDataSource) {
// targetKeys展示在右边列表的数据集
if (this.splitedDataSource) {
return this.splitedDataSource;
}
var _props = this.props,
rowKey = _props.rowKey,
dataSource = _props.dataSource,
_props$targetKeys2 = _props.targetKeys,
targetKeys = _props$targetKeys2 === undefined ? [] : _props$targetKeys2;
var rowKey = this.props.rowKey;
var targetKeys = newTargetKeys || this.props.targetKeys;
//异步加载数据源时
var dataSource = newDataSource || this.props.dataSource;
if (rowKey) {
dataSource.forEach(function (record) {
record.key = rowKey(record);
@ -169,25 +193,30 @@ var Transfer = function (_React$Component) {
var key = _ref.key;
return targetKeys.indexOf(key) === -1;
});
var rightDataSource = [];
targetKeys.forEach(function (targetKey) {
var targetItem = dataSource.filter(function (record) {
return record.key === targetKey;
})[0];
if (targetItem) {
rightDataSource.push(targetItem);
}
var rightDataSource = dataSource.filter(function (_ref2) {
var key = _ref2.key;
return targetKeys.indexOf(key) > -1;
});
this.splitedDataSource = {
leftDataSource: leftDataSource,
rightDataSource: rightDataSource
};
this.setState({
leftDataSource: leftDataSource,
rightDataSource: rightDataSource
});
return this.splitedDataSource;
};
/**
* List中的item选中/未选中状态改变时触发
* @param {*} direction 'left' or 'right'
* @param {*} holder 更新后的'sourceSelectedKeys' or 'targetSelectedKeys'
*/
Transfer.prototype.handleSelectChange = function handleSelectChange(direction, holder) {
// onSelectChange当选中的item发生改变时的回调 参数(sourceSelectedKeys, targetSelectedKeys)
var _state2 = this.state,
sourceSelectedKeys = _state2.sourceSelectedKeys,
targetSelectedKeys = _state2.targetSelectedKeys;
@ -204,36 +233,76 @@ var Transfer = function (_React$Component) {
}
};
/**
* 左侧列表全选事件
* @param filteredDataSource dataSource中刨去设置为disabled的部分
* @param checkAll 是否是全选状态 true全选
*/
/**
* 搜索框值更改事件
* @param direction 'left' or 'right'
* @param value 输入的值
*/
/**
* 清空搜索框内容
* @param direction 'left' or 'right'
*/
/**
* 点击list item选中或取消选中
* @param direction 'left' or 'right'
* @param selectedItem 选中的item的信息和dataSource数据源中的item信息一致
* @param checked 是否已勾选true已勾选 false未勾选
*/
Transfer.prototype.getSelectedKeysName = function getSelectedKeysName(direction) {
return direction === 'left' ? 'sourceSelectedKeys' : 'targetSelectedKeys';
};
/**
* 拖拽结束时触发
*/
/**
* 拖拽开始时触发
*/
Transfer.prototype.render = function render() {
var _props2 = this.props,
_props2$prefixCls = _props2.prefixCls,
prefixCls = _props2$prefixCls === undefined ? 'u-transfer' : _props2$prefixCls,
_props2$operations = _props2.operations,
operations = _props2$operations === undefined ? [] : _props2$operations,
showSearch = _props2.showSearch,
notFoundContent = _props2.notFoundContent,
searchPlaceholder = _props2.searchPlaceholder,
body = _props2.body,
footer = _props2.footer,
listStyle = _props2.listStyle,
_props2$className = _props2.className,
className = _props2$className === undefined ? '' : _props2$className,
filterOption = _props2.filterOption,
render = _props2.render,
lazy = _props2.lazy;
var _props = this.props,
_props$prefixCls = _props.prefixCls,
prefixCls = _props$prefixCls === undefined ? 'u-transfer' : _props$prefixCls,
_props$operations = _props.operations,
operations = _props$operations === undefined ? [] : _props$operations,
showSearch = _props.showSearch,
notFoundContent = _props.notFoundContent,
searchPlaceholder = _props.searchPlaceholder,
body = _props.body,
footer = _props.footer,
listStyle = _props.listStyle,
_props$className = _props.className,
className = _props$className === undefined ? '' : _props$className,
filterOption = _props.filterOption,
render = _props.render,
lazy = _props.lazy,
showCheckbox = _props.showCheckbox,
draggable = _props.draggable;
var _state3 = this.state,
leftFilter = _state3.leftFilter,
rightFilter = _state3.rightFilter,
sourceSelectedKeys = _state3.sourceSelectedKeys,
targetSelectedKeys = _state3.targetSelectedKeys;
targetSelectedKeys = _state3.targetSelectedKeys,
leftDataSource = _state3.leftDataSource,
rightDataSource = _state3.rightDataSource;
var _splitDataSource = this.splitDataSource(this.props),
leftDataSource = _splitDataSource.leftDataSource,
rightDataSource = _splitDataSource.rightDataSource;
// const { leftDataSource, rightDataSource } = this.splitDataSource(this.props);
var leftActive = targetSelectedKeys.length > 0;
var rightActive = sourceSelectedKeys.length > 0;
@ -244,55 +313,65 @@ var Transfer = function (_React$Component) {
return _react2["default"].createElement(
'div',
{ className: cls },
_react2["default"].createElement(_list2["default"], {
titleText: titles[0],
dataSource: leftDataSource,
filter: leftFilter,
filterOption: filterOption,
style: listStyle,
checkedKeys: sourceSelectedKeys,
handleFilter: this.handleLeftFilter,
handleClear: this.handleLeftClear,
handleSelect: this.handleLeftSelect,
handleSelectAll: this.handleLeftSelectAll,
render: render,
showSearch: showSearch,
searchPlaceholder: searchPlaceholder,
notFoundContent: notFoundContent,
body: body,
footer: footer,
prefixCls: prefixCls + '-list',
lazy: lazy
}),
_react2["default"].createElement(_operation2["default"], {
rightActive: rightActive,
rightArrowText: operations[0],
moveToRight: this.moveToRight,
leftActive: leftActive,
leftArrowText: operations[1],
moveToLeft: this.moveToLeft,
className: prefixCls + '-operation'
}),
_react2["default"].createElement(_list2["default"], {
titleText: titles[1],
dataSource: rightDataSource,
filter: rightFilter,
filterOption: filterOption,
style: listStyle,
checkedKeys: targetSelectedKeys,
handleFilter: this.handleRightFilter,
handleClear: this.handleRightClear,
handleSelect: this.handleRightSelect,
handleSelectAll: this.handleRightSelectAll,
render: render,
showSearch: showSearch,
searchPlaceholder: searchPlaceholder,
notFoundContent: notFoundContent,
body: body,
footer: footer,
prefixCls: prefixCls + '-list',
lazy: lazy
})
_react2["default"].createElement(
_reactBeautifulDnd.DragDropContext,
{ onDragEnd: this.onDragEnd, onDragStart: this.onDragStart },
_react2["default"].createElement(_list2["default"], {
titleText: titles[0] //左侧标题
, dataSource: leftDataSource //左侧数据源
, filter: leftFilter //搜索框中输入的内容
, filterOption: filterOption //搜索过滤方法 参数(inputValue, option)
, style: listStyle //自定义的columns的样式表
, checkedKeys: sourceSelectedKeys //左侧已勾选的item的keys
, handleFilter: this.handleLeftFilter //左侧搜索框值更改事件
, handleClear: this.handleLeftClear //清空左侧搜索框内容
, handleSelect: this.handleLeftSelect //点击左侧列表中的item改变选中或取消选中状态
, handleSelectAll: this.handleLeftSelectAll //点击左侧全选
, render: render,
showSearch: showSearch //是否显示搜索框
, searchPlaceholder: searchPlaceholder //搜索框placeholder
, notFoundContent: notFoundContent //当没有相关内容的显示内容
, body: body,
footer: footer,
prefixCls: prefixCls + '-list',
lazy: lazy,
showCheckbox: showCheckbox,
draggable: draggable,
id: '1'
}),
!draggable ? _react2["default"].createElement(_operation2["default"], {
rightActive: rightActive,
rightArrowText: operations[0],
moveToRight: this.moveToRight,
leftActive: leftActive,
leftArrowText: operations[1],
moveToLeft: this.moveToLeft,
className: prefixCls + '-operation'
}) : '',
_react2["default"].createElement(_list2["default"], {
titleText: titles[1] //右侧标题
, dataSource: rightDataSource //右侧数据源
, filter: rightFilter //搜索框中输入的内容
, filterOption: filterOption //搜索过滤方法 参数(inputValue, option)
, style: listStyle //自定义的columns的样式表
, checkedKeys: targetSelectedKeys //右侧已勾选的item的keys
, handleFilter: this.handleRightFilter //右侧搜索框值更改事件
, handleClear: this.handleRightClear //清空右侧搜索框内容
, handleSelect: this.handleRightSelect //点击右侧列表中的item改变选中或取消选中状态
, handleSelectAll: this.handleRightSelectAll //点击右侧全选
, render: render,
showSearch: showSearch //是否显示搜索框
, searchPlaceholder: searchPlaceholder //搜索框placeholder
, notFoundContent: notFoundContent //当没有相关内容的显示内容
, body: body,
footer: footer,
prefixCls: prefixCls + '-list',
lazy: lazy,
showCheckbox: showCheckbox,
draggable: draggable,
id: '2'
})
)
);
};
@ -303,10 +382,12 @@ var _initialiseProps = function _initialiseProps() {
var _this2 = this;
this.moveTo = function (direction) {
var _props3 = _this2.props,
_props3$targetKeys = _props3.targetKeys,
targetKeys = _props3$targetKeys === undefined ? [] : _props3$targetKeys,
onChange = _props3.onChange;
var _props2 = _this2.props,
_props2$targetKeys = _props2.targetKeys,
targetKeys = _props2$targetKeys === undefined ? [] : _props2$targetKeys,
onChange = _props2.onChange;
// debugger
var _state4 = _this2.state,
sourceSelectedKeys = _state4.sourceSelectedKeys,
targetSelectedKeys = _state4.targetSelectedKeys;
@ -320,11 +401,13 @@ var _initialiseProps = function _initialiseProps() {
// empty checked keys
var oppositeDirection = direction === 'right' ? 'left' : 'right';
_this2.setState(_defineProperty({}, _this2.getSelectedKeysName(oppositeDirection), []));
// debugger
_this2.handleSelectChange(oppositeDirection, []);
if (onChange) {
onChange(newTargetKeys, direction, moveKeys);
}
_this2.splitDataSource(newTargetKeys);
};
this.moveToLeft = function () {
@ -347,23 +430,23 @@ var _initialiseProps = function _initialiseProps() {
};
this.handleLeftSelectAll = function (filteredDataSource, checkAll) {
return _this2.handleSelectAll('left', filteredDataSource, checkAll);
_this2.handleSelectAll('left', filteredDataSource, checkAll);
};
this.handleRightSelectAll = function (filteredDataSource, checkAll) {
return _this2.handleSelectAll('right', filteredDataSource, checkAll);
};
this.handleFilter = function (direction, e) {
_this2.setState(_defineProperty({}, direction + 'Filter', e));
this.handleFilter = function (direction, value) {
_this2.setState(_defineProperty({}, direction + 'Filter', value));
};
this.handleLeftFilter = function (e) {
return _this2.handleFilter('left', e);
this.handleLeftFilter = function (v) {
return _this2.handleFilter('left', value);
};
this.handleRightFilter = function (e) {
return _this2.handleFilter('right', e);
this.handleRightFilter = function (v) {
return _this2.handleFilter('right', value);
};
this.handleClear = function (direction) {
@ -385,10 +468,11 @@ var _initialiseProps = function _initialiseProps() {
var holder = direction === 'left' ? [].concat(_toConsumableArray(sourceSelectedKeys)) : [].concat(_toConsumableArray(targetSelectedKeys));
var index = holder.indexOf(selectedItem.key);
if (index > -1) {
holder.splice(index, 1);
}
if (checked) {
//已勾选
holder.splice(index, 1);
} else if (index === -1) {
//未勾选
holder.push(selectedItem.key);
}
_this2.handleSelectChange(direction, holder);
@ -415,6 +499,81 @@ var _initialiseProps = function _initialiseProps() {
}
return defaultTitles;
};
this.id2List = {
droppable_1: 'leftDataSource',
droppable_2: 'rightDataSource'
};
this.getList = function (id) {
return _this2.state[_this2.id2List[id]];
};
this.onDragEnd = function (result) {
var source = result.source,
destination = result.destination,
draggableId = result.draggableId;
var _props3 = _this2.props,
targetKeys = _props3.targetKeys,
onChange = _props3.onChange;
var sourceIndex = source.index; //初始位置
var disIndex = destination.index; //移动后的位置
var temp = void 0; //拖拽的元素
// dropped outside the list
if (!destination) {
return;
}
// 从右往左拖拽 或 在左侧列表中拖拽
if (destination.droppableId === 'droppable_1') {
if (source.droppableId === destination.droppableId) return;
_this2.moveToLeft();
return;
}
// 在右侧列表中上下拖拽进行排序
if (source.droppableId === destination.droppableId) {
var items = (0, _utils.reorder)(_this2.getList(source.droppableId), targetKeys, source.index, destination.index);
var state = { leftDataSource: items.dataArr };
if (source.droppableId === 'droppable_2') {
state = { rightDataSource: items.dataArr };
}
state.sourceSelectedKeys = [];
state.targetSelectedKeys = [];
_this2.setState(state);
if (onChange) {
onChange(items.targetKeyArr, "", draggableId);
}
} else {
// 从左往右拖拽
var _result = (0, _utils.move)(_this2.getList(source.droppableId), _this2.getList(destination.droppableId), source, destination, targetKeys);
if (onChange) {
onChange(_result.newTargetKeys, "", draggableId);
}
_this2.setState({
leftDataSource: _result.droppable_1,
rightDataSource: _result.droppable_2,
sourceSelectedKeys: [],
targetSelectedKeys: []
});
}
};
this.onDragStart = function (result) {
var selectedItem = {};
var source = result.source;
selectedItem.key = result.draggableId;
if (source.droppableId === 'droppable_1') {
// leftMenu
_this2.handleLeftSelect(selectedItem);
} else if (source.droppableId === 'droppable_2') {
// rightMenu
_this2.handleRightSelect(selectedItem);
}
};
};
Transfer.List = Transfer.List;

View File

@ -48,34 +48,26 @@ var Item = function (_React$Component) {
_inherits(Item, _React$Component);
function Item() {
var _temp, _this, _ret;
_classCallCheck(this, Item);
return _possibleConstructorReturn(this, _React$Component.apply(this, arguments));
}
Item.prototype.shouldComponentUpdate = function shouldComponentUpdate() {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = _possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this), _this.matchFilter = function (text) {
var _this$props = _this.props,
filter = _this$props.filter,
filterOption = _this$props.filterOption,
item = _this$props.item;
if (filterOption) {
return filterOption(filter, item);
}
return text.indexOf(filter) >= 0;
}, _temp), _possibleConstructorReturn(_this, _ret);
}
Item.prototype.shouldComponentUpdate = function shouldComponentUpdate() {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return _PureRenderMixin2["default"].shouldComponentUpdate.apply(this, args);
};
// matchFilter = (text) => {
// const { filter, filterOption, item } = this.props;
// if (filterOption) {
// return filterOption(filter, item);
// }
// return text.indexOf(filter) >= 0;
// }
Item.prototype.render = function render() {
var _classNames;
@ -89,9 +81,10 @@ var Item = function (_React$Component) {
prefixCls = _props.prefixCls,
onClick = _props.onClick,
renderedText = _props.renderedText,
renderedEl = _props.renderedEl;
renderedEl = _props.renderedEl,
showCheckbox = _props.showCheckbox;
var className = (0, _classnames2["default"])((_classNames = {}, _defineProperty(_classNames, prefixCls + '-content-item', true), _defineProperty(_classNames, prefixCls + '-content-item-disabled', item.disabled), _classNames));
var className = (0, _classnames2["default"])((_classNames = {}, _defineProperty(_classNames, prefixCls + '-content-item', true), _defineProperty(_classNames, prefixCls + '-content-item-disabled', item.disabled), _defineProperty(_classNames, prefixCls + '-content-item-selected', checked), _classNames));
var lazyProps = (0, _objectAssign2["default"])({
height: 32,
@ -137,9 +130,9 @@ var Item = function (_React$Component) {
return onClick(item);
}
},
_react2["default"].createElement(_beeCheckbox2["default"], { checked: checked, disabled: item.disabled, onClick: item.disabled ? undefined : function () {
showCheckbox ? _react2["default"].createElement(_beeCheckbox2["default"], { checked: checked, disabled: item.disabled, onClick: item.disabled ? undefined : function () {
return onClick(item);
} }),
} }) : '',
_react2["default"].createElement(
'span',
null,

View File

@ -4,6 +4,8 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
@ -38,6 +40,14 @@ var _beeCheckbox = require('bee-checkbox');
var _beeCheckbox2 = _interopRequireDefault(_beeCheckbox);
var _beeIcon = require('bee-icon');
var _beeIcon2 = _interopRequireDefault(_beeIcon);
var _reactBeautifulDnd = require('react-beautiful-dnd');
var _tinperBeeCore = require('tinper-bee-core');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _defaults(obj, defaults) { var keys = Object.getOwnPropertyNames(defaults); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = Object.getOwnPropertyDescriptor(defaults, key); if (value && value.configurable && obj[key] === undefined) { Object.defineProperty(obj, key, value); } } return obj; }
@ -71,6 +81,8 @@ var TransferList = function (_React$Component) {
var _this = _possibleConstructorReturn(this, _React$Component.call(this, props));
_this.matchFilter = function (text, item) {
//filter搜索框中的内容
//filterOption用户自定义的搜索过滤方法
var _this$props = _this.props,
filter = _this$props.filter,
filterOption = _this$props.filterOption;
@ -82,12 +94,14 @@ var TransferList = function (_React$Component) {
};
_this.handleSelect = function (selectedItem) {
// checkedKeys已勾选的Keys数组
// result是否已勾选true已勾选 false未勾选
var checkedKeys = _this.props.checkedKeys;
var result = checkedKeys.some(function (key) {
return key === selectedItem.key;
});
_this.props.handleSelect(selectedItem, !result);
_this.props.handleSelect(selectedItem, result);
};
_this.handleFilter = function (e) {
@ -110,6 +124,29 @@ var TransferList = function (_React$Component) {
};
};
_this.onKeyDown = function (event, provided, snapshot, item) {
if (provided.dragHandleProps) {
provided.dragHandleProps.onKeyDown(event);
}
if (event.defaultPrevented) {
return;
}
if (snapshot.isDragging) {
return;
}
if (event.keyCode !== _tinperBeeCore.KeyCode.ENTER) {
return;
}
// 为了选择,我们使用此事件 we are using the event for selection
event.preventDefault();
_this.performAction(event, item);
};
_this.state = {
mounted: false
};
@ -138,19 +175,29 @@ var TransferList = function (_React$Component) {
return _PureRenderMixin2["default"].shouldComponentUpdate.apply(this, args);
};
/**
* 获取Checkbox状态
* @param {*} filteredDataSource dataSource中刨去设置为disabled的部分
*/
TransferList.prototype.getCheckStatus = function getCheckStatus(filteredDataSource) {
var checkedKeys = this.props.checkedKeys;
if (checkedKeys.length === 0) {
return 'none';
return 'none'; //全部未选
} else if (filteredDataSource.every(function (item) {
return checkedKeys.indexOf(item.key) >= 0;
})) {
return 'all';
return 'all'; //全部已选
}
return 'part';
return 'part'; //部分已选
};
/**
* 点击list item选中或取消选中
* @param selectedItem 选中的item的信息和dataSource数据源中的item信息一致
*/
TransferList.prototype.renderCheckbox = function renderCheckbox(_ref) {
var _classNames,
_this3 = this;
@ -162,7 +209,7 @@ var TransferList = function (_React$Component) {
disabled = _ref.disabled,
checkable = _ref.checkable;
var checkAll = !checkPart && checked;
var checkAll = !checkPart && checked; //非半选 && 全选
prefixCls = "u";
var checkboxCls = (0, _classnames2["default"])((_classNames = {}, _defineProperty(_classNames, prefixCls + '-checkbox-indeterminate', checkPart), _defineProperty(_classNames, prefixCls + '-checkbox-disabled', disabled), _classNames));
return _react2["default"].createElement(
@ -181,7 +228,8 @@ var TransferList = function (_React$Component) {
};
TransferList.prototype.render = function render() {
var _this4 = this;
var _classNames2,
_this4 = this;
var _props = this.props,
prefixCls = _props.prefixCls,
@ -198,7 +246,10 @@ var TransferList = function (_React$Component) {
showSearch = _props.showSearch,
_props$render = _props.render,
render = _props$render === undefined ? noop : _props$render,
style = _props.style;
style = _props.style,
id = _props.id,
showCheckbox = _props.showCheckbox,
draggable = _props.draggable;
var _props2 = this.props,
searchPlaceholder = _props2.searchPlaceholder,
notFoundContent = _props2.notFoundContent;
@ -208,11 +259,11 @@ var TransferList = function (_React$Component) {
var footerDom = footer((0, _objectAssign2["default"])({}, this.props));
var bodyDom = body((0, _objectAssign2["default"])({}, this.props));
var listCls = (0, _classnames2["default"])(prefixCls, _defineProperty({}, prefixCls + '-with-footer', !!footerDom));
var listCls = (0, _classnames2["default"])(prefixCls, (_classNames2 = {}, _defineProperty(_classNames2, prefixCls + '-with-footer', !!footerDom), _defineProperty(_classNames2, prefixCls + '-draggable', !!draggable), _classNames2));
var filteredDataSource = [];
var totalDataSource = [];
var showItems = dataSource.map(function (item) {
var showItems = dataSource.map(function (item, index) {
var _renderItem = _this4.renderItem(item),
renderedText = _renderItem.renderedText,
renderedEl = _renderItem.renderedEl;
@ -229,19 +280,41 @@ var TransferList = function (_React$Component) {
}
var checked = checkedKeys.indexOf(item.key) >= 0;
return _react2["default"].createElement(_item2["default"], {
key: item.key,
item: item,
lazy: lazy,
render: render,
renderedText: renderedText,
renderedEl: renderedEl,
filter: filter,
filterOption: filterOption,
checked: checked,
prefixCls: prefixCls,
onClick: _this4.handleSelect
});
return _react2["default"].createElement(
_reactBeautifulDnd.Draggable,
{ key: item.key, index: index, draggableId: '' + item.key, isDragDisabled: draggable ? item.disabled : !draggable },
function (provided, snapshot) {
return _react2["default"].createElement(
'div',
_extends({
ref: provided.innerRef
}, provided.draggableProps, provided.dragHandleProps, {
// onClick={(event) =>this.handleDrag(event, provided, snapshot, item)}
onKeyDown: function onKeyDown(event) {
return _this4.onKeyDown(event, provided, snapshot, item);
}
// className={classnames({
// ...getClass(this.props,snapshot.isDragging).drag
// })}
, style: _extends({}, provided.draggableProps.style) }),
_react2["default"].createElement(_item2["default"]
// ref={provided.innerRef} //Error: provided.innerRef has not been provided with a HTMLElement
// key={item.key}
, { item: item,
lazy: lazy,
render: render,
renderedText: renderedText,
renderedEl: renderedEl,
filter: filter,
filterOption: filterOption,
checked: checked,
prefixCls: prefixCls,
onClick: _this4.handleSelect,
showCheckbox: showCheckbox
})
);
}
);
});
var unit = '';
@ -262,7 +335,7 @@ var TransferList = function (_React$Component) {
prefixCls: prefixCls + '-search',
onChange: this.handleFilter,
handleClear: this.handleClear,
placeholder: searchPlaceholder || 'Search',
placeholder: searchPlaceholder,
value: filter
})
) : null;
@ -272,19 +345,42 @@ var TransferList = function (_React$Component) {
{ className: showSearch ? prefixCls + '-body ' + prefixCls + '-body-with-search' : prefixCls + '-body' },
search,
_react2["default"].createElement(
_beeAnimate2["default"],
{
component: 'ul',
className: prefixCls + '-content',
transitionName: this.state.mounted ? prefixCls + '-content-item-highlight' : '',
transitionLeave: false
},
showItems
_reactBeautifulDnd.Droppable,
{ droppableId: 'droppable_' + id, direction: 'vertical', isDropDisabled: !draggable },
function (provided, snapshot) {
return _react2["default"].createElement(
'div',
{ ref: provided.innerRef, isDraggingOver: snapshot.isDraggingOver, key: id, className: prefixCls + '-content' },
_react2["default"].createElement(
_beeAnimate2["default"],
{
component: 'ul',
transitionName: _this4.state.mounted ? prefixCls + '-content-item-highlight' : '',
transitionLeave: false
},
showItems
),
_react2["default"].createElement(
'div',
{ className: prefixCls + '-delete-selected ' + (snapshot.isDraggingOver && id === '1' ? 'show' : '') },
_react2["default"].createElement(
'div',
{ className: prefixCls + '-del-btn' },
_react2["default"].createElement(_beeIcon2["default"], { type: 'uf-arrow-down-2' }),
_react2["default"].createElement(
'span',
null,
'\u79FB\u9664\u5DF2\u9009'
)
)
)
);
}
),
_react2["default"].createElement(
'div',
{ className: prefixCls + '-body-not-found' },
notFoundContent || 'Not Found'
{ className: prefixCls + '-body-not-found ' + (dataSource.length == 0 ? "show" : "") },
notFoundContent
)
);
@ -309,7 +405,7 @@ var TransferList = function (_React$Component) {
_react2["default"].createElement(
'div',
{ className: prefixCls + '-header' },
renderedCheckbox,
showCheckbox ? renderedCheckbox : '',
_react2["default"].createElement(
'span',
{ className: prefixCls + '-header-selected' },

View File

@ -88,6 +88,7 @@ var Search = function (_React$Component) {
'div',
null,
_react2["default"].createElement(_beeFormControl2["default"], {
size: 'sm',
placeholder: placeholder,
className: prefixCls,
value: value,

67
build/utils.js Normal file
View File

@ -0,0 +1,67 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
/**
* a little function to help us with reordering the result
* @param {*} list
* @param {*} targetKeys
* @param {*} startIndex
* @param {*} endIndex
*/
var reorder = function reorder(list, targetKeys, startIndex, endIndex) {
var result1 = Array.from(list);
var _result1$splice = result1.splice(startIndex, 1),
_result1$splice2 = _slicedToArray(_result1$splice, 1),
removed1 = _result1$splice2[0];
result1.splice(endIndex, 0, removed1);
var result2 = Array.from(targetKeys);
var _result2$splice = result2.splice(startIndex, 1),
_result2$splice2 = _slicedToArray(_result2$splice, 1),
removed2 = _result2$splice2[0];
result2.splice(endIndex, 0, removed2);
var result = {};
result.dataArr = result1;
result.targetKeyArr = result2;
return result;
};
/**
* Moves an item from one list to another list.
* @param {*} source
* @param {*} destination
* @param {*} droppableSource
* @param {*} droppableDestination
* @param {*} targetKeys
*/
var move = function move(source, destination, droppableSource, droppableDestination, targetKeys) {
var sourceClone = Array.from(source);
var destClone = Array.from(destination);
var _sourceClone$splice = sourceClone.splice(droppableSource.index, 1),
_sourceClone$splice2 = _slicedToArray(_sourceClone$splice, 1),
removed = _sourceClone$splice2[0];
destClone.splice(droppableDestination.index, 0, removed);
targetKeys.splice(droppableDestination.index, 0, removed.key);
var result = {};
result[droppableSource.droppableId] = sourceClone;
result[droppableDestination.droppableId] = destClone;
result.newTargetKeys = targetKeys;
return result;
};
exports.reorder = reorder;
exports.move = move;

View File

@ -1,75 +1,75 @@
/**
*
* @title 常用可选transfer
* @description
*
*/
import React, { Component } from 'react';
import Transfer from '../../src';
const mockData = [];
for (let i = 0; i < 20; i++) {
mockData.push({
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
disabled: i % 3 < 1,
});
}
const targetKeys = mockData
.filter(item => +item.key % 3 > 1)
.map(item => item.key);
class Demo1 extends React.Component {
state = {
targetKeys,
selectedKeys: [],
showModal: false,
modalSize: ''
}
handleChange = (nextTargetKeys, direction, moveKeys) => {
this.setState({ targetKeys: nextTargetKeys });
console.log('targetKeys: ', targetKeys);
console.log('direction: ', direction);
console.log('moveKeys: ', moveKeys);
}
handleSelectChange = (sourceSelectedKeys, targetSelectedKeys) => {
this.setState({ selectedKeys: [...sourceSelectedKeys, ...targetSelectedKeys] });
console.log('sourceSelectedKeys: ', sourceSelectedKeys);
console.log('targetSelectedKeys: ', targetSelectedKeys);
}
handleScroll = (direction, e) => {
console.log('direction:', direction);
console.log('target:', e.target);
}
render() {
const state = this.state;
return (
<Transfer
dataSource={mockData}
titles={['Source', 'Target']}
targetKeys={state.targetKeys}
selectedKeys={state.selectedKeys}
onChange={this.handleChange}
onSelectChange={this.handleSelectChange}
onScroll={this.handleScroll}
render={item => item.title}
/>
);
}
}
export default Demo1
/**
*
* @title 常用可选transfer
* @description targetKeys需要通过ES6的扩展运算符进行赋值实现对象的深拷贝
*
*/
import React, { Component } from 'react';
import Transfer from '../../src';
const mockData = [];
for (let i = 0; i < 20; i++) {
mockData.push({
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
disabled: i % 3 < 1,
});
}
const targetKeys = mockData
.filter(item => +item.key % 3 > 1)
.map(item => item.key);
class Demo1 extends React.Component {
state = {
targetKeys,
selectedKeys: [],
showModal: false,
modalSize: ''
}
handleChange = (nextTargetKeys, direction, moveKeys) => {
this.setState({ targetKeys: nextTargetKeys });
console.log('targetKeys: ', nextTargetKeys);
console.log('direction: ', direction);
console.log('moveKeys: ', moveKeys);
}
handleSelectChange = (sourceSelectedKeys, targetSelectedKeys) => {
this.setState({ selectedKeys: [...sourceSelectedKeys, ...targetSelectedKeys] });
console.log('sourceSelectedKeys: ', sourceSelectedKeys);
console.log('targetSelectedKeys: ', targetSelectedKeys);
}
handleScroll = (direction, e) => {
console.log('direction:', direction);
console.log('target:', e.target);
}
render() {
const state = this.state;
const targetKeys = [...this.state.targetKeys];
return (
<Transfer
dataSource={mockData}
titles={['Source', 'Target']}
targetKeys={targetKeys}
selectedKeys={state.selectedKeys}
onChange={this.handleChange}
onSelectChange={this.handleSelectChange}
onScroll={this.handleScroll}
render={item => item.title}
/>
);
}
}
export default Demo1

View File

@ -1,58 +1,58 @@
/**
*
* @title 带搜索框的tranfer
* @description
*
*/
import React, { Component } from 'react';
import Transfer from '../../src';
class Demo2 extends React.Component {
state = {
mockData: [],
targetKeys: [],
}
componentDidMount() {
this.getMock();
}
getMock = () => {
const targetKeys = [];
const mockData = [];
for (let i = 0; i < 20; i++) {
const data = {
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
chosen: Math.random() * 2 > 1,
};
if (data.chosen) {
targetKeys.push(data.key);
}
mockData.push(data);
}
this.setState({ mockData, targetKeys });
}
filterOption = (inputValue, option) => {
return option.title.indexOf(inputValue) > -1;
}
handleChange = (targetKeys) => {
this.setState({ targetKeys });
}
render() {
return (
<Transfer
dataSource={this.state.mockData}
showSearch
filterOption={this.filterOption}
targetKeys={this.state.targetKeys}
onChange={this.handleChange}
render={item => item.title}
/>
);
}
}
export default Demo2
/**
*
* @title 带搜索框的tranfer
* @description
*
*/
import React, { Component } from 'react';
import Transfer from '../../src';
class Demo2 extends React.Component {
state = {
mockData: [],
targetKeys: [],
}
componentDidMount() {
this.getMock();
}
getMock = () => {
const targetKeys = [];
const mockData = [];
for (let i = 0; i < 20; i++) {
const data = {
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
chosen: Math.random() * 2 > 1,
};
if (data.chosen) {
targetKeys.push(data.key);
}
mockData.push(data);
}
this.setState({ mockData, targetKeys });
}
filterOption = (inputValue, option) => {
return option.title.indexOf(inputValue) > -1;
}
handleChange = (targetKeys) => {
this.setState({ targetKeys });
}
render() {
return (
<Transfer
dataSource={this.state.mockData}
showSearch
filterOption={this.filterOption}
targetKeys={this.state.targetKeys}
onChange={this.handleChange}
render={item => item.title}
/>
);
}
}
export default Demo2

View File

@ -1,69 +1,69 @@
/**
*
* @title 底部自定义的transfer
* @description
*
*/
import React, { Component } from 'react';
import Button from 'bee-button';
import Transfer from '../../src';
class Demo3 extends React.Component {
state = {
mockData: [],
targetKeys: [],
}
componentDidMount() {
this.getMock();
}
getMock = () => {
const targetKeys = [];
const mockData = [];
for (let i = 0; i < 20; i++) {
const data = {
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
chosen: Math.random() * 2 > 1,
};
if (data.chosen) {
targetKeys.push(data.key);
}
mockData.push(data);
}
this.setState({ mockData, targetKeys });
}
handleChange = (targetKeys) => {
this.setState({ targetKeys });
}
renderFooter = () => {
return (
<Button
size="sm"
style={{ float: 'right', margin: 5 }}
onClick={this.getMock}
>
reload
</Button>
);
}
render() {
return (
<Transfer
dataSource={this.state.mockData}
showSearch
listStyle={{
width: 250,
height: 300,
}}
targetKeys={this.state.targetKeys}
onChange={this.handleChange}
render={item => `${item.title}-${item.description}`}
footer={this.renderFooter}
/>
);
}
}
export default Demo3
/**
*
* @title 底部自定义的transfer
* @description
*
*/
import React, { Component } from 'react';
import Button from 'bee-button';
import Transfer from '../../src';
class Demo3 extends React.Component {
state = {
mockData: [],
targetKeys: [],
}
componentDidMount() {
this.getMock();
}
getMock = () => {
const targetKeys = [];
const mockData = [];
for (let i = 0; i < 20; i++) {
const data = {
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
chosen: Math.random() * 2 > 1,
};
if (data.chosen) {
targetKeys.push(data.key);
}
mockData.push(data);
}
this.setState({ mockData, targetKeys });
}
handleChange = (targetKeys) => {
this.setState({ targetKeys });
}
renderFooter = () => {
return (
<Button
size="sm"
style={{ float: 'right', margin: 5 }}
onClick={this.getMock}
>
reload
</Button>
);
}
render() {
return (
<Transfer
dataSource={this.state.mockData}
showSearch
listStyle={{
width: 250,
height: 300,
}}
targetKeys={this.state.targetKeys}
onChange={this.handleChange}
render={item => `${item.title}-${item.description}`}
footer={this.renderFooter}
/>
);
}
}
export default Demo3

76
demo/demolist/Demo4.js Normal file
View File

@ -0,0 +1,76 @@
/**
*
* @title 隐藏复选框
* @description 通过`showCheckbox`参数控制复选框显示和隐藏
*
*/
import React, { Component } from 'react';
import Transfer from '../../src';
const mockData = [];
for (let i = 0; i < 20; i++) {
mockData.push({
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
disabled: i % 3 < 1,
});
}
const targetKeys = mockData
.filter(item => +item.key % 3 > 1)
.map(item => item.key);
class Demo4 extends React.Component {
state = {
targetKeys,
selectedKeys: [],
showModal: false,
modalSize: ''
}
handleChange = (nextTargetKeys, direction, moveKeys) => {
this.setState({ targetKeys: nextTargetKeys });
console.log('targetKeys: ', targetKeys);
console.log('direction: ', direction);
console.log('moveKeys: ', moveKeys);
}
handleSelectChange = (sourceSelectedKeys, targetSelectedKeys) => {
this.setState({ selectedKeys: [...sourceSelectedKeys, ...targetSelectedKeys] });
console.log('sourceSelectedKeys: ', sourceSelectedKeys);
console.log('targetSelectedKeys: ', targetSelectedKeys);
}
handleScroll = (direction, e) => {
console.log('direction:', direction);
console.log('target:', e.target);
}
render() {
const state = this.state;
return (
<Transfer
dataSource={mockData}
showCheckbox={false}
titles={['Source', 'Target']}
targetKeys={state.targetKeys}
selectedKeys={state.selectedKeys}
onChange={this.handleChange}
onSelectChange={this.handleSelectChange}
onScroll={this.handleScroll}
render={item => item.title}
/>
);
}
}
export default Demo4;

78
demo/demolist/Demo5.js Normal file
View File

@ -0,0 +1,78 @@
/**
*
* @title 拖拽穿梭
* @description 通过`draggable`参数设置是否可以通过拖拽进行穿梭和排序
*
*/
import React, { Component } from 'react';
import Transfer from '../../src';
const mockData = [];
for (let i = 0; i < 20; i++) {
mockData.push({
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
disabled: i % 3 < 1,
});
}
const targetKeys = mockData
.filter(item => +item.key % 3 > 1)
.map(item => item.key);
class Demo5 extends React.Component {
state = {
targetKeys,
selectedKeys: [],
showModal: false,
modalSize: ''
}
handleChange = (nextTargetKeys, direction, moveKeys) => {
this.setState({ targetKeys: nextTargetKeys });
console.log('targetKeys: ', nextTargetKeys);
console.log('direction: ', direction);
console.log('moveKeys: ', moveKeys);
}
handleSelectChange = (sourceSelectedKeys, targetSelectedKeys) => {
this.setState({ selectedKeys: [...sourceSelectedKeys, ...targetSelectedKeys] });
console.log('sourceSelectedKeys: ', sourceSelectedKeys);
console.log('targetSelectedKeys: ', targetSelectedKeys);
}
handleScroll = (direction, e) => {
console.log('direction:', direction);
console.log('target:', e.target);
}
render() {
const state = this.state;
// targetKeys需要通过数组的扩展运算符进行赋值
const targetKeys = [...this.state.targetKeys];
return (
<Transfer
draggable={true}
showCheckbox={false}
dataSource={mockData}
titles={['Source', 'Target']}
targetKeys={targetKeys}
selectedKeys={state.selectedKeys}
onChange={this.handleChange}
onSelectChange={this.handleSelectChange}
onScroll={this.handleScroll}
render={item => item.title}
/>
);
}
}
export default Demo5

File diff suppressed because one or more lines are too long

1669
dist/demo.css vendored

File diff suppressed because it is too large Load Diff

2
dist/demo.css.map vendored

File diff suppressed because one or more lines are too long

12707
dist/demo.js vendored

File diff suppressed because one or more lines are too long

2
dist/demo.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -1,50 +1,52 @@
# 穿梭框 Transfer
两框之间的元素迁移,非常直观且有效。一个或多个元素选择后点击方向按钮转到另一列框中。左栏是“源”,右边是“目标”
## 何时使用
需要两框之间的元素迁移时
## 如何使用
```
import { Transfer } from 'tinper-bee';
or
import Transfer from 'bee-transfer';
import bee-transfer/build/Transfer.css;
```
## 代码演示
## API
|参数|说明|类型|默认值|
|:--|:---:|:--:|---:|
|dataSource|设置数据源。当有targetKey props存在时dataSource的数据刨去targetKey数据,剩下的都放在左边列表|[]|[]|
|render|自定义的展示出来的item,需要展示哪些字段|Function(record)|-|
|targetKeys|展示在右边列表的数据集|[]|[]|
|selectedKeys|所有选中的item的keys|[]|[]|
|onChange|当item在穿梭成功后的回调 参数(targetKeys, direction, moveKeys)|func|-|
|onSelectChange| 当选中的item发生改变时的回调 参数(sourceSelectedKeys, targetSelectedKeys)|fun|-|
|onScroll| 当滑动可选的item列表的回调 参数(direction, event)|func|-|
|listStyle|自定义的columns的样式表|object |-|
|className|class|string|''|''|
|titles|两columns的title|[]|-|
|operations|自定义按钮操作|[]|'>', '<'|
|showSearch|是否显示搜索框|boolean |false|
|filterOption|搜索过滤方法 参数(inputValue, option)|func或者boolean |-|
|searchPlaceholder|搜索框的默认显示文字|string|'Search here'|
|notFoundContent|当没有相关内容的显示内容|string或ReactNode| 'The list is empty'|
|footer|渲染底部的dom|ReactNode|-|
|lazy|懒加载dom|object|当tranfer放在bee-modal里 添加参数 lazy={container:"modal"}|
|onSearchChange|当搜索域变化的回调函数 参数(direction: 'left'|'right', event: Event)|func|-|
## 注意事项
暂无
## 更新日志
# 穿梭框 Transfer
两框之间的元素迁移,非常直观且有效。一个或多个元素选择后点击方向按钮转到另一列框中。左栏是“源”,右边是“目标”
## 何时使用
需要两框之间的元素迁移时
## 如何使用
```
import { Transfer } from 'tinper-bee';
or
import Transfer from 'bee-transfer';
import 'bee-transfer/build/Transfer.css';
```
## 代码演示
## API
|参数|说明|类型|默认值|
|:--|:---:|:--:|---:|
|dataSource|设置数据源。当有targetKey props存在时dataSource的数据刨去targetKey数据,剩下的都放在左边列表|[]|[]|
|render|自定义的展示出来的item,需要展示哪些字段|Function(record)|-|
|targetKeys|展示在右边列表的数据集|[]|[]|
|selectedKeys|所有选中的item的keys|[]|[]|
|onChange|当item在穿梭成功后的回调 参数(targetKeys, direction, moveKeys)|func|-|
|onSelectChange| 当选中的item发生改变时的回调 参数(sourceSelectedKeys, targetSelectedKeys)|fun|-|
|onScroll| 当滑动可选的item列表的回调 参数(direction, event)|func|-|
|listStyle|自定义的columns的样式表|object |-|
|className|class|string|''|''|
|titles|两columns的title|[]|-|
|operations|自定义按钮操作|[]|'>', '<'|
|showSearch|是否显示搜索框|boolean |false|
|filterOption|搜索过滤方法 参数(inputValue, option)|func或者boolean |-|
|searchPlaceholder|搜索框的默认显示文字|string|'Search'|
|notFoundContent|当没有相关内容的显示内容|string或ReactNode| 'Not Found'|
|footer|渲染底部的dom|ReactNode|-|
|lazy|懒加载dom|object|当tranfer放在bee-modal里 添加参数 lazy={container:"modal"}|
|onSearchChange|当搜索域变化的回调函数 参数(direction: 'left'|'right', event: Event)|func|-|
|showCheckbox|是否显示Checkbox复选框|bool|true|
|draggable|是否可以通过拖拽进行穿梭和排序|bool|false|
## 注意事项
暂无
## 更新日志

2111
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,70 +1,71 @@
{
"name": "bee-transfer",
"version": "2.0.7",
"description": "Transfer ui component for react",
"keywords": [
"react",
"react-component",
"bee-transfer",
"iuap-design",
"tinper-bee",
"Transfer"
],
"engines": {
"node": ">=4.0.0"
},
"homepage": "https://github.com/tinper-bee/bee-transfer.git",
"author": "Yonyou FED",
"repository": "http://github.com/tinper-bee/bee-transfer",
"bugs": "https://github.com/tinper-bee/bee-transfer.git/issues",
"license": "MIT",
"main": "./build/index.js",
"config": {
"port": 3000,
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"scripts": {
"dev": "bee-tools run start",
"build": "bee-tools run build",
"lint": "bee-tools-test run lint",
"test": "bee-tools-test run test",
"chrome": "bee-tools-test run chrome",
"coveralls": "bee-tools-test run coverage",
"browsers": "bee-tools-test run browsers",
"pub": "bee-tools run pub"
},
"dependencies": {
"babel-runtime": "^6.23.0",
"bee-animate": "^1.0.0",
"bee-button": "latest",
"bee-checkbox": "latest",
"bee-form-control": "latest",
"bee-icon": "^1.0.4",
"classnames": "^2.2.5",
"react-lazy-load": "^3.0.12",
"shallowequal": "^1.0.2",
"tinper-bee-core": "latest"
},
"peerDependencies": {
"react": "^15.3.0 || ^16.0",
"react-dom": "^15.3.0 || ^16.0",
"prop-types": "15.6.0"
},
"devDependencies": {
"bee-button": "latest",
"bee-clipboard": "^2.0.0",
"bee-drawer": "0.0.2",
"bee-layout": "latest",
"bee-panel": "latest",
"chai": "^3.5.0",
"console-polyfill": "~0.2.1",
"cz-conventional-changelog": "^2.1.0",
"enzyme": "^2.4.1",
"es5-shim": "~4.1.10",
"react": "15.3.2",
"react-addons-test-utils": "15.3.2",
"react-dom": "15.3.2"
}
}
{
"name": "bee-transfer",
"version": "2.0.7",
"description": "Transfer ui component for react",
"keywords": [
"react",
"react-component",
"bee-transfer",
"iuap-design",
"tinper-bee",
"Transfer"
],
"engines": {
"node": ">=4.0.0"
},
"homepage": "https://github.com/tinper-bee/bee-transfer.git",
"author": "Yonyou FED",
"repository": "http://github.com/tinper-bee/bee-transfer",
"bugs": "https://github.com/tinper-bee/bee-transfer.git/issues",
"license": "MIT",
"main": "./build/index.js",
"config": {
"port": 3000,
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"scripts": {
"dev": "bee-tools run start",
"build": "bee-tools run build",
"lint": "bee-tools-test run lint",
"test": "bee-tools-test run test",
"chrome": "bee-tools-test run chrome",
"coveralls": "bee-tools-test run coverage",
"browsers": "bee-tools-test run browsers",
"pub": "bee-tools run pub"
},
"dependencies": {
"babel-runtime": "^6.23.0",
"bee-animate": "^1.0.0",
"bee-button": "latest",
"bee-checkbox": "latest",
"bee-form-control": "latest",
"bee-icon": "^1.0.4",
"classnames": "^2.2.5",
"react-beautiful-dnd": "^9.0.2",
"react-lazy-load": "^3.0.12",
"shallowequal": "^1.0.2",
"tinper-bee-core": "^2.0.28"
},
"peerDependencies": {
"react": "^15.3.0 || ^16.0",
"react-dom": "^15.3.0 || ^16.0",
"prop-types": "15.6.0"
},
"devDependencies": {
"bee-button": "latest",
"bee-clipboard": "^2.0.0",
"bee-drawer": "0.0.2",
"bee-layout": "latest",
"bee-panel": "latest",
"chai": "^3.5.0",
"console-polyfill": "~0.2.1",
"cz-conventional-changelog": "^2.1.0",
"enzyme": "^2.4.1",
"es5-shim": "~4.1.10",
"react": "15.3.2",
"react-addons-test-utils": "15.3.2",
"react-dom": "15.3.2"
}
}

View File

@ -1,301 +1,443 @@
import React from 'react';
import classNames from 'classnames';
import List from './list';
import Operation from './operation';
import Search from './search';
import PropTypes from 'prop-types';
function noop() {
}
const defaultProps = {
dataSource: [],
render: noop,
showSearch: false,
};
const propTypes = {
prefixCls: PropTypes.string,
dataSource: PropTypes.array,
render: PropTypes.func,
targetKeys: PropTypes.array,
onChange: PropTypes.func,
height: PropTypes.number,
listStyle: PropTypes.object,
className: PropTypes.string,
titles: PropTypes.array,
operations: PropTypes.array,
showSearch: PropTypes.bool,
filterOption: PropTypes.func,
searchPlaceholder: PropTypes.string,
notFoundContent: PropTypes.node,
body: PropTypes.func,
footer: PropTypes.func,
rowKey: PropTypes.func,
lazy: PropTypes.object,
};
const defaultTitles = ['', ''];
class Transfer extends React.Component{
constructor(props) {
super(props);
const { selectedKeys = [], targetKeys = [] } = props;
this.state = {
leftFilter: '',
rightFilter: '',
sourceSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) === -1),
targetSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) > -1),
};
}
componentWillReceiveProps(nextProps) {
const { sourceSelectedKeys, targetSelectedKeys } = this.state;
if (nextProps.targetKeys !== this.props.targetKeys ||
nextProps.dataSource !== this.props.dataSource) {
// clear cached splited dataSource
this.splitedDataSource = null;
const { dataSource, targetKeys = [] } = nextProps;
function existInDateSourcekey(key) {
return dataSource.filter(item => item.key === key).length;
}
// clear key nolonger existed
// clear checkedKeys according to targetKeys
this.setState({
sourceSelectedKeys: sourceSelectedKeys.filter(existInDateSourcekey)
.filter(data => targetKeys.filter(key => key === data).length === 0),
targetSelectedKeys: targetSelectedKeys.filter(existInDateSourcekey)
.filter(data => targetKeys.filter(key => key === data).length > 0),
});
}
if (nextProps.selectedKeys) {
const targetKeys = nextProps.targetKeys;
this.setState({
sourceSelectedKeys: nextProps.selectedKeys.filter(key => targetKeys.indexOf(key) === -1),
targetSelectedKeys: nextProps.selectedKeys.filter(key => targetKeys.indexOf(key) > -1),
});
}
}
splitDataSource() {
if (this.splitedDataSource) {
return this.splitedDataSource;
}
const { rowKey, dataSource, targetKeys = [] } = this.props;
if (rowKey) {
dataSource.forEach(record => {
record.key = rowKey(record);
});
}
const leftDataSource = dataSource.filter(({ key }) => targetKeys.indexOf(key) === -1);
const rightDataSource = [];
targetKeys.forEach((targetKey) => {
const targetItem = dataSource.filter(record => record.key === targetKey)[0];
if (targetItem) {
rightDataSource.push(targetItem);
}
});
this.splitedDataSource = {
leftDataSource,
rightDataSource,
};
return this.splitedDataSource;
}
moveTo = (direction) => {
const { targetKeys = [], onChange } = this.props;
const { sourceSelectedKeys, targetSelectedKeys } = this.state;
const moveKeys = direction === 'right' ? sourceSelectedKeys : targetSelectedKeys;
// move items to target box
const newTargetKeys = direction === 'right'
? moveKeys.concat(targetKeys)
: targetKeys.filter(targetKey => moveKeys.indexOf(targetKey) === -1);
// empty checked keys
const oppositeDirection = direction === 'right' ? 'left' : 'right';
this.setState({
[this.getSelectedKeysName(oppositeDirection)]: [],
});
this.handleSelectChange(oppositeDirection, []);
if (onChange) {
onChange(newTargetKeys, direction, moveKeys);
}
}
moveToLeft = () => this.moveTo('left')
moveToRight = () => this.moveTo('right')
handleSelectChange(direction, holder) {
const { sourceSelectedKeys, targetSelectedKeys } = this.state;
const onSelectChange = this.props.onSelectChange;
if (!onSelectChange) {
return;
}
if (direction === 'left') {
onSelectChange(holder, targetSelectedKeys);
} else {
onSelectChange(sourceSelectedKeys, holder);
}
}
handleSelectAll = (direction, filteredDataSource, checkAll) => {
const holder = checkAll ? [] : filteredDataSource.map(item => item.key);
this.handleSelectChange(direction, holder);
if (!this.props.selectedKeys) {
this.setState({
[this.getSelectedKeysName(direction)]: holder,
});
}
}
handleLeftSelectAll = (filteredDataSource, checkAll) => (
this.handleSelectAll('left', filteredDataSource, checkAll)
)
handleRightSelectAll = (filteredDataSource, checkAll) => (
this.handleSelectAll('right', filteredDataSource, checkAll)
)
handleFilter = (direction, e) => {
this.setState({
// add filter
[`${direction}Filter`]: e,
});
}
handleLeftFilter = (e) => this.handleFilter('left', e)
handleRightFilter = (e) => this.handleFilter('right', e)
handleClear = (direction) => {
this.setState({
[`${direction}Filter`]: '',
});
}
handleLeftClear = () => this.handleClear('left')
handleRightClear = () => this.handleClear('right')
handleSelect = (direction, selectedItem, checked) => {
const { sourceSelectedKeys, targetSelectedKeys } = this.state;
const holder = direction === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys];
const index = holder.indexOf(selectedItem.key);
if (index > -1) {
holder.splice(index, 1);
}
if (checked) {
holder.push(selectedItem.key);
}
this.handleSelectChange(direction, holder);
if (!this.props.selectedKeys) {
this.setState({
[this.getSelectedKeysName(direction)]: holder,
});
}
}
handleLeftSelect = (selectedItem, checked) => this.handleSelect('left', selectedItem, checked);
handleRightSelect = (selectedItem, checked) => this.handleSelect('right', selectedItem, checked);
getTitles = () => {
if (this.props.titles) {
return this.props.titles;
}
if (this.context &&
this.context.antLocale &&
this.context.antLocale.Transfer
) {
return this.context.antLocale.Transfer.titles || [];
}
return defaultTitles;
}
getSelectedKeysName(direction) {
return direction === 'left' ? 'sourceSelectedKeys' : 'targetSelectedKeys';
}
render() {
const {
prefixCls = 'u-transfer', operations = [], showSearch, notFoundContent,
searchPlaceholder, body, footer, listStyle, className = '',
filterOption, render, lazy
} = this.props;
const { leftFilter, rightFilter, sourceSelectedKeys, targetSelectedKeys } = this.state;
const { leftDataSource, rightDataSource } = this.splitDataSource(this.props);
const leftActive = targetSelectedKeys.length > 0;
const rightActive = sourceSelectedKeys.length > 0;
const cls = classNames(className, prefixCls);
const titles = this.getTitles();
return (
<div className={cls}>
<List
titleText={titles[0]}
dataSource={leftDataSource}
filter={leftFilter}
filterOption={filterOption}
style={listStyle}
checkedKeys={sourceSelectedKeys}
handleFilter={this.handleLeftFilter}
handleClear={this.handleLeftClear}
handleSelect={this.handleLeftSelect}
handleSelectAll={this.handleLeftSelectAll}
render={render}
showSearch={showSearch}
searchPlaceholder={searchPlaceholder}
notFoundContent={notFoundContent}
body={body}
footer={footer}
prefixCls={`${prefixCls}-list`}
lazy={lazy}
/>
<Operation
rightActive={rightActive}
rightArrowText={operations[0]}
moveToRight={this.moveToRight}
leftActive={leftActive}
leftArrowText={operations[1]}
moveToLeft={this.moveToLeft}
className={`${prefixCls}-operation`}
/>
<List
titleText={titles[1]}
dataSource={rightDataSource}
filter={rightFilter}
filterOption={filterOption}
style={listStyle}
checkedKeys={targetSelectedKeys}
handleFilter={this.handleRightFilter}
handleClear={this.handleRightClear}
handleSelect={this.handleRightSelect}
handleSelectAll={this.handleRightSelectAll}
render={render}
showSearch={showSearch}
searchPlaceholder={searchPlaceholder}
notFoundContent={notFoundContent}
body={body}
footer={footer}
prefixCls={`${prefixCls}-list`}
lazy={lazy}
/>
</div>
);
}
}
Transfer.List = Transfer.List;
Transfer.Operation = Transfer.Operation;
Transfer.Search = Transfer.Search;
Transfer.propTypes = propTypes;
Transfer.defaultProps = defaultProps;
export default Transfer;
import React from 'react';
import classNames from 'classnames';
import List from './list';
import Operation from './operation';
import Search from './search';
import PropTypes from 'prop-types';
import { DragDropContext } from 'react-beautiful-dnd';
import { reorder,move } from './utils';
function noop() {}
const defaultProps = {
dataSource: [],
render: noop,
showSearch: false,
searchPlaceholder: 'Search',
notFoundContent: 'Not Found',
showCheckbox: true,
draggable: false
};
const propTypes = {
prefixCls: PropTypes.string,
dataSource: PropTypes.array,
render: PropTypes.func,
targetKeys: PropTypes.array,
onChange: PropTypes.func,
height: PropTypes.number,
listStyle: PropTypes.object,
className: PropTypes.string,
titles: PropTypes.array,
operations: PropTypes.array,
showSearch: PropTypes.bool,
filterOption: PropTypes.func,
searchPlaceholder: PropTypes.string,
notFoundContent: PropTypes.node,
body: PropTypes.func,
footer: PropTypes.func,
rowKey: PropTypes.func,
lazy: PropTypes.object,
showCheckbox: PropTypes.bool,
draggable: PropTypes.bool
};
const defaultTitles = ['', ''];
class Transfer extends React.Component{
constructor(props) {
super(props);
const { selectedKeys = [], targetKeys = [] } = props;
this.state = {
leftFilter: '',
rightFilter: '',
sourceSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) === -1),
targetSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) > -1),
leftDataSource: [],
rightDataSource: []
};
this.cacheTargetKeys = [...targetKeys];
}
componentDidMount(){
this.splitDataSource();
}
componentWillReceiveProps(nextProps) {
const { sourceSelectedKeys, targetSelectedKeys } = this.state;
if (nextProps.targetKeys !== this.props.targetKeys ||
nextProps.dataSource !== this.props.dataSource ||
nextProps.targetKeys !== this.cacheTargetKeys) {
// clear cached splited dataSource
this.splitedDataSource = null;
const { dataSource, targetKeys = [] } = nextProps;
function existInDateSourcekey(key) {
return dataSource.filter(item => item.key === key).length;
}
// clear key nolonger existed
// clear checkedKeys according to targetKeys
this.setState({
sourceSelectedKeys: sourceSelectedKeys.filter(existInDateSourcekey)
.filter(data => targetKeys.filter(key => key === data).length === 0),
targetSelectedKeys: targetSelectedKeys.filter(existInDateSourcekey)
.filter(data => targetKeys.filter(key => key === data).length > 0),
});
this.splitDataSource(targetKeys,dataSource);
}
if (nextProps.selectedKeys) {
const targetKeys = nextProps.targetKeys;
this.setState({
sourceSelectedKeys: nextProps.selectedKeys.filter(key => targetKeys.indexOf(key) === -1),
targetSelectedKeys: nextProps.selectedKeys.filter(key => targetKeys.indexOf(key) > -1),
});
}
}
/**
* 从dataSource中分离出leftDataSource和rightDataSource
* @param {*} newTargetKeys 更新后的targetKeys
* @param {*} newDataSource 异步加载数据源时从nextProps中获取的dataSource
*/
splitDataSource(newTargetKeys, newDataSource) {
// targetKeys展示在右边列表的数据集
if (this.splitedDataSource) {
return this.splitedDataSource;
}
const { rowKey } = this.props;
let targetKeys = newTargetKeys || this.props.targetKeys;
//异步加载数据源时
let dataSource = newDataSource || this.props.dataSource;
if (rowKey) {
dataSource.forEach(record => {
record.key = rowKey(record);
});
}
const leftDataSource = dataSource.filter(({ key }) => targetKeys.indexOf(key) === -1);
const rightDataSource = dataSource.filter(({key}) => targetKeys.indexOf(key) > -1);
this.splitedDataSource = {
leftDataSource,
rightDataSource,
};
this.setState({
leftDataSource,
rightDataSource,
})
return this.splitedDataSource;
}
moveTo = (direction) => {
const { targetKeys = [], onChange } = this.props;
// debugger
const { sourceSelectedKeys, targetSelectedKeys } = this.state;
const moveKeys = direction === 'right' ? sourceSelectedKeys : targetSelectedKeys;
// move items to target box
const newTargetKeys = direction === 'right'
? moveKeys.concat(targetKeys)
: targetKeys.filter(targetKey => moveKeys.indexOf(targetKey) === -1);
// empty checked keys
const oppositeDirection = direction === 'right' ? 'left' : 'right';
this.setState({
[this.getSelectedKeysName(oppositeDirection)]: [],
});
// debugger
this.handleSelectChange(oppositeDirection, []);
if (onChange) {
onChange(newTargetKeys, direction, moveKeys);
}
this.splitDataSource(newTargetKeys);
}
moveToLeft = () => this.moveTo('left')
moveToRight = () => this.moveTo('right')
/**
* List中的item选中/未选中状态改变时触发
* @param {*} direction 'left' or 'right'
* @param {*} holder 更新后的'sourceSelectedKeys' or 'targetSelectedKeys'
*/
handleSelectChange(direction, holder) {
// onSelectChange当选中的item发生改变时的回调 参数(sourceSelectedKeys, targetSelectedKeys)
const { sourceSelectedKeys, targetSelectedKeys } = this.state;
const onSelectChange = this.props.onSelectChange;
if (!onSelectChange) {
return;
}
if (direction === 'left') {
onSelectChange(holder, targetSelectedKeys);
} else {
onSelectChange(sourceSelectedKeys, holder);
}
}
handleSelectAll = (direction, filteredDataSource, checkAll) => {
const holder = checkAll ? [] : filteredDataSource.map(item => item.key);
this.handleSelectChange(direction, holder);
if (!this.props.selectedKeys) {
this.setState({
[this.getSelectedKeysName(direction)]: holder,
});
}
}
/**
* 左侧列表全选事件
* @param filteredDataSource dataSource中刨去设置为disabled的部分
* @param checkAll 是否是全选状态 true全选
*/
handleLeftSelectAll = (filteredDataSource, checkAll) => {
this.handleSelectAll('left', filteredDataSource, checkAll)
}
handleRightSelectAll = (filteredDataSource, checkAll) => (
this.handleSelectAll('right', filteredDataSource, checkAll)
)
/**
* 搜索框值更改事件
* @param direction 'left' or 'right'
* @param value 输入的值
*/
handleFilter = (direction, value) => {
this.setState({
// add filter
[`${direction}Filter`]: value,
});
}
handleLeftFilter = (v) => this.handleFilter('left', value)
handleRightFilter = (v) => this.handleFilter('right', value)
/**
* 清空搜索框内容
* @param direction 'left' or 'right'
*/
handleClear = (direction) => {
this.setState({
[`${direction}Filter`]: '',
});
}
handleLeftClear = () => this.handleClear('left')
handleRightClear = () => this.handleClear('right')
/**
* 点击list item选中或取消选中
* @param direction 'left' or 'right'
* @param selectedItem 选中的item的信息和dataSource数据源中的item信息一致
* @param checked 是否已勾选true已勾选 false未勾选
*/
handleSelect = (direction, selectedItem, checked) => {
const { sourceSelectedKeys, targetSelectedKeys } = this.state;
const holder = direction === 'left' ? [...sourceSelectedKeys] : [...targetSelectedKeys];
const index = holder.indexOf(selectedItem.key);
if (checked) { //已勾选
holder.splice(index, 1);
}else if(index === -1){ //未勾选
holder.push(selectedItem.key);
}
this.handleSelectChange(direction, holder);
if (!this.props.selectedKeys) {
this.setState({
[this.getSelectedKeysName(direction)]: holder,
});
}
}
handleLeftSelect = (selectedItem, checked) => this.handleSelect('left', selectedItem, checked);
handleRightSelect = (selectedItem, checked) => this.handleSelect('right', selectedItem, checked);
getTitles = () => {
if (this.props.titles) {
return this.props.titles;
}
if (this.context &&
this.context.antLocale &&
this.context.antLocale.Transfer
) {
return this.context.antLocale.Transfer.titles || [];
}
return defaultTitles;
}
getSelectedKeysName(direction) {
return direction === 'left' ? 'sourceSelectedKeys' : 'targetSelectedKeys';
}
id2List = {
droppable_1: 'leftDataSource',
droppable_2: 'rightDataSource'
};
getList = id => this.state[this.id2List[id]];
/**
* 拖拽结束时触发
*/
onDragEnd = result => {
const { source, destination,draggableId } = result;
let { targetKeys, onChange } = this.props;
let sourceIndex = source.index; //初始位置
let disIndex = destination.index; //移动后的位置
let temp; //拖拽的元素
// dropped outside the list
if (!destination) {
return;
}
// 从右往左拖拽 或 在左侧列表中拖拽
if (destination.droppableId === 'droppable_1') {
if(source.droppableId === destination.droppableId) return;
this.moveToLeft();
return;
}
// 在右侧列表中上下拖拽进行排序
if (source.droppableId === destination.droppableId) {
const items = reorder(
this.getList(source.droppableId),
targetKeys,
source.index,
destination.index
);
let state = { leftDataSource:items.dataArr }
if (source.droppableId === 'droppable_2'){
state = { rightDataSource:items.dataArr }
}
state.sourceSelectedKeys = [];
state.targetSelectedKeys = [];
this.setState(state);
if (onChange) {
onChange(items.targetKeyArr, "", draggableId);
}
} else { // 从左往右拖拽
const result = move(
this.getList(source.droppableId),
this.getList(destination.droppableId),
source,
destination,
targetKeys
)
if (onChange) {
onChange(result.newTargetKeys, "", draggableId);
}
this.setState({
leftDataSource: result.droppable_1,
rightDataSource: result.droppable_2,
sourceSelectedKeys: [],
targetSelectedKeys: []
})
}
};
/**
* 拖拽开始时触发
*/
onDragStart = result => {
let selectedItem = {};
const { source } = result;
selectedItem.key = result.draggableId;
if(source.droppableId === 'droppable_1'){ // leftMenu
this.handleLeftSelect(selectedItem);
}else if(source.droppableId === 'droppable_2'){ // rightMenu
this.handleRightSelect(selectedItem);
}
}
render() {
const {
prefixCls = 'u-transfer', operations = [], showSearch, notFoundContent,
searchPlaceholder, body, footer, listStyle, className = '',
filterOption, render, lazy, showCheckbox, draggable
} = this.props;
const { leftFilter, rightFilter, sourceSelectedKeys, targetSelectedKeys, leftDataSource, rightDataSource } = this.state;
// const { leftDataSource, rightDataSource } = this.splitDataSource(this.props);
const leftActive = targetSelectedKeys.length > 0;
const rightActive = sourceSelectedKeys.length > 0;
const cls = classNames(className, prefixCls);
const titles = this.getTitles();
return (
<div className={cls}>
<DragDropContext onDragEnd={this.onDragEnd} onDragStart={this.onDragStart} >
<List
titleText={titles[0]} //左侧标题
dataSource={leftDataSource} //左侧数据源
filter={leftFilter} //搜索框中输入的内容
filterOption={filterOption} //搜索过滤方法 参数(inputValue, option)
style={listStyle} //自定义的columns的样式表
checkedKeys={sourceSelectedKeys} //左侧已勾选的item的keys
handleFilter={this.handleLeftFilter} //左侧搜索框值更改事件
handleClear={this.handleLeftClear} //清空左侧搜索框内容
handleSelect={this.handleLeftSelect} //点击左侧列表中的item改变选中或取消选中状态
handleSelectAll={this.handleLeftSelectAll} //点击左侧全选
render={render}
showSearch={showSearch} //是否显示搜索框
searchPlaceholder={searchPlaceholder} //搜索框placeholder
notFoundContent={notFoundContent} //当没有相关内容的显示内容
body={body}
footer={footer}
prefixCls={`${prefixCls}-list`}
lazy={lazy}
showCheckbox={showCheckbox}
draggable={draggable}
id={'1'}
/>
{!draggable?
<Operation
rightActive={rightActive}
rightArrowText={operations[0]}
moveToRight={this.moveToRight}
leftActive={leftActive}
leftArrowText={operations[1]}
moveToLeft={this.moveToLeft}
className={`${prefixCls}-operation`}
/>
: ''
}
<List
titleText={titles[1]} //右侧标题
dataSource={rightDataSource} //右侧数据源
filter={rightFilter} //搜索框中输入的内容
filterOption={filterOption} //搜索过滤方法 参数(inputValue, option)
style={listStyle} //自定义的columns的样式表
checkedKeys={targetSelectedKeys} //右侧已勾选的item的keys
handleFilter={this.handleRightFilter} //右侧搜索框值更改事件
handleClear={this.handleRightClear} //清空右侧搜索框内容
handleSelect={this.handleRightSelect} //点击右侧列表中的item改变选中或取消选中状态
handleSelectAll={this.handleRightSelectAll} //点击右侧全选
render={render}
showSearch={showSearch} //是否显示搜索框
searchPlaceholder={searchPlaceholder} //搜索框placeholder
notFoundContent={notFoundContent} //当没有相关内容的显示内容
body={body}
footer={footer}
prefixCls={`${prefixCls}-list`}
lazy={lazy}
showCheckbox={showCheckbox}
draggable={draggable}
id={'2'}
/>
</DragDropContext>
</div>
);
}
}
Transfer.List = Transfer.List;
Transfer.Operation = Transfer.Operation;
Transfer.Search = Transfer.Search;
Transfer.propTypes = propTypes;
Transfer.defaultProps = defaultProps;
export default Transfer;

View File

@ -1,235 +1,282 @@
@import "../node_modules/tinper-bee-core/scss/minxin-variables";
@import "../node_modules/tinper-bee-core/scss/minxin-mixins";
@import '../node_modules/bee-button/src/Button.scss';
@import '../node_modules/bee-form-control/src/FormControl.scss';
@import '../node_modules/bee-checkbox/src/Checkbox.scss';
@import "../node_modules/bee-icon/src/Icon.scss";
.u-transfer {
position: relative;
line-height: 1.5;
&-list {
font-size: 12px;
border: 1px solid $transfer-border-gap-color;
display: inline-block;
border-radius: $border-radius-base;
vertical-align: middle;
position: relative;
width: 180px;
height: 200px;
padding-top: 33px;
&-with-footer {
padding-bottom: 33px;
}
&-search {
&-action {
color: #ccc;
position: absolute;
top: 2px;
right: 2px;
width: 32px;
height: 32px;
line-height: 32px;
text-align: center;
font-size: 14px;
text-decoration: none;
.uf {
transition: all .3s;
font-size: 12px;
color: #ccc;
&:hover {
color: rgba(0,0,0,.43);
}
}
}
}
&-header {
padding: 7px 15px;
border-radius: $border-radius-base $border-radius-base 0 0;
background: #fff;
color: rgba(0,0,0,.65);
border-bottom: 1px solid #e9e9e9;//$transfer-border-gap-color;
overflow: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
&-title {
position: absolute;
right: 15px;
}
}
&-body {
font-size: 12px;
position: relative;
height: 100%;
&-search-wrapper {
position: absolute;
top: 0;
left: 0;
height: 28px;
padding: 4px;
width: 100%;
}
}
&-body-with-search {
padding-top: 34px;
}
&-content {
height: 100%;
overflow: auto;
margin: 0;
padding: 0;
&-item {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding: 7px 15px;
min-height: 32px;
transition: all 0.3s ease;
}
&-item:not(&-item-disabled):hover {
cursor: pointer;
background-color: $hover-bg-color-base;
}
&-item-disabled {
cursor: not-allowed;
color: rgba(0,0,0,.25);
}
&-item-highlight-enter {
animation: transferHighlightIn 1s ease;
transition: none;
}
}
&-body-not-found {
padding-top: 0;
color: #ccc;
text-align: center;
display: none;
position: absolute;
top: 50%;
width: 100%;
margin-top: -10px;
}
&-content:empty + &-body-not-found {
display: block;
}
&-footer {
border-top: 1px solid #e9e9e9;
border-radius: 0 0 $select-border-radius $select-border-radius;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
}
}
&-operation {
display: inline-block;
overflow: hidden;
margin: 0 8px;
vertical-align: middle;
button {
display: block;
&:first-child {
margin-bottom: 4px;
}
.uf {
font-size: 12px;
}
}
}
.u-checkbox+span, .u-checkbox-wrapper+span {
padding-left: 8px;
padding-right: 8px;
}
// .u-checkbox-checked .u-checkbox-label, .u-checkbox-indeterminate .u-checkbox-label {
// background-color: #108ee9;
// border-color: #108ee9;
// }
.u-checkbox.u-checkbox-indeterminate .u-checkbox-label:after {
color: #fff;
content: "\e6ce";
line-height: 18px;
font-size: 14px;
}
.u-checkbox .u-checkbox-label:before, .u-checkbox .u-checkbox-label:after{
top: 0;
}
// .u-checkbox.u-checkbox-indeterminate .u-checkbox-label:before {
// box-shadow: inset 0 0 0 10px rgb(30,136,229);
// border-color: rgb(30,136,229);
// }
.u-button.disabled, .u-button[disabled]:hover {
background: #fff;
color:#9a9898;
border-color: rgb(224,224,224);
}
}
.u-transfer-operation .u-button-sm {
padding: 0;
font-size: 1.2rem;
border: 1px solid;
color: rgb(109, 107, 107);
min-width: 3rem;
}
.u-transfer-operation .u-button-sm{
background: $checkbox-color;
color: #fff;
border-color: $checkbox-color;
}
.u-transfer-operation .u-button-sm:hover{
background: $checkbox-color;
border-color: $checkbox-color;
}
.u-transfer-operation .u-button-sm[disabled]{
background: #fff;
color:#9a9898;
border-color: rgb(224,224,224);
}
.u-transfer-list-search {
font-size: 1.2rem;
border-color: $transfer-border-gap-color;
height: 26px;
}
@keyframes transferHighlightIn {
0% {
background: $checkbox-color;
}
100% {
background: transparent;
}
}
@import "../node_modules/tinper-bee-core/scss/minxin-variables";
@import "../node_modules/tinper-bee-core/scss/minxin-mixins";
// @import '../node_modules/bee-button/src/Button.scss';
// @import '../node_modules/bee-form-control/src/FormControl.scss';
// @import '../node_modules/bee-checkbox/src/Checkbox.scss';
// @import "../node_modules/bee-icon/src/Icon.scss";
.u-transfer {
position: relative;
line-height: 1.5;
&-list {
position: relative;
font-size: 12px;
border: 1px solid $transfer-border-gap-color;
display: inline-block;
border-radius: $border-radius-base;
vertical-align: middle;
position: relative;
width: 180px;
height: 200px;
padding-top: 33px;
&.u-transfer-list-draggable:first-child{
margin-right: 16px;
}
&-with-footer {
padding-bottom: 33px;
}
&-search {
&-action {
color: #505F79;
position: absolute;
top: 2px;
right: 2px;
width: 26px;
height: 26px;
line-height: 26px;
text-align: center;
font-size: 16px;
text-decoration: none;
.uf {
transition: all .3s;
font-size: 16px;
color: #505F79;
&:hover {
color: rgba(0,0,0,.43);
}
}
}
}
&-header {
padding: 7px 15px;
border-radius: $border-radius-base $border-radius 0 0;
background: #fff;
color: $font-color-base;
border-bottom: 1px solid #e9e9e9;//$transfer-border-gap-color;
overflow: hidden;
position: absolute;
top: 0;
left: 0;
width: 100%;
&-title {
position: absolute;
right: 15px;
}
}
&-body {
font-size: 12px;
position: relative;
height: 100%;
&-search-wrapper {
position: absolute;
top: 0;
left: 0;
padding: 4px;
width: 100%;
}
}
&-body-with-search {
padding-top: 34px;
}
&-content {
height: 100%;
overflow: auto;
margin: 0;
padding: 0;
&.delbtn-backdrop{
background: #505F79;
opacity: 0.65;
&.uf-del{
color: #fff;
}
}
&-item {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
padding: 7px 15px;
min-height: 32px;
transition: all 0.3s ease;
color: $font-color-base;
}
&-item-selected{
background: $selected-bg-color-base;
}
&-item:not(&-item-disabled):hover {
cursor: pointer;
background-color: $hover-bg-color-base;
}
&-item-disabled {
cursor: not-allowed;
color: $disabled-color-base;
}
&-item-highlight-enter {
animation: transferHighlightIn 1s ease;
transition: none;
}
}
&-delete-selected{
display: none;
width: 100%;
height: 100%;
background: rgba(52, 69, 99, 0.2);
position: absolute;
left: 0;
top: 0;
color: #fff;
&.show{
display: block;
}
.u-transfer-list-del-btn{
width:78px;
height:78px;
position: absolute;
left: 50%;
top: 50%;
margin-top: -39px;
margin-left: -39px;
padding: 13px;
background:rgba(94,108,132,1);
border-radius:$border-radius-base;
text-align: center;
vertical-align: middle;
span{
display: block;
}
}
}
&-body-not-found {
padding-top: 0;
color: #ccc;
text-align: center;
display: none;
position: absolute;
top: 50%;
width: 100%;
margin-top: -10px;
&.show{
display: block;
}
}
&-content:empty + &-body-not-found {
display: block;
}
&-footer {
border-top: 1px solid #e9e9e9;
border-radius: 0 0 $select-border-radius $select-border-radius;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
}
}
&-operation {
display: inline-block;
overflow: hidden;
margin: 0 8px;
vertical-align: middle;
button {
display: block;
&:first-child {
margin-bottom: 4px;
}
.uf {
font-size: 12px;
}
}
}
.u-checkbox+span, .u-checkbox-wrapper+span {
padding-left: 8px;
padding-right: 8px;
}
// .u-checkbox-checked .u-checkbox-label, .u-checkbox-indeterminate .u-checkbox-label {
// background-color: #108ee9;
// border-color: #108ee9;
// }
.u-checkbox.u-checkbox-indeterminate .u-checkbox-label:after {
color: #fff;
content: "\e6ce";
line-height: 18px;
font-size: 14px;
}
.u-checkbox .u-checkbox-label:before, .u-checkbox .u-checkbox-label:after{
top: 0;
}
// .u-checkbox.u-checkbox-indeterminate .u-checkbox-label:before {
// box-shadow: inset 0 0 0 10px rgb(30,136,229);
// border-color: rgb(30,136,229);
// }
.u-button.disabled, .u-button[disabled]:hover {
background: #fff;
color:#9a9898;
border-color: rgb(224,224,224);
}
}
.u-transfer-operation .u-button-sm {
padding: 0;
font-size: 1.2rem;
border: 1px solid;
color: rgb(109, 107, 107);
min-width: 3rem;
}
.u-transfer-operation .u-button-sm{
background: $checkbox-color;
color: #fff;
border-color: $checkbox-color;
}
.u-transfer-operation .u-button-sm:hover{
background: $checkbox-color;
border-color: $checkbox-color;
}
.u-transfer-operation .u-button-sm[disabled]{
background: #fff;
color:#9a9898;
border-color: rgb(224,224,224);
}
.u-transfer-list-search {
font-size: 1.2rem;
border-color: $transfer-border-gap-color;
height: 26px;
}
@keyframes transferHighlightIn {
0% {
background: $checkbox-color;
}
100% {
background: transparent;
}
}

View File

@ -1,73 +1,78 @@
import React from 'react';
import classNames from 'classnames';
import PureRenderMixin from './PureRenderMixin';
import assign from 'object-assign';
import Lazyload from 'react-lazy-load';
import Checkbox from 'bee-checkbox';
function isRenderResultPlainObject(result) {
return result && !React.isValidElement(result) &&
Object.prototype.toString.call(result) === '[object Object]';
}
class Item extends React.Component{
shouldComponentUpdate(...args) {
return PureRenderMixin.shouldComponentUpdate.apply(this, args);
}
matchFilter = (text) => {
const { filter, filterOption, item } = this.props;
if (filterOption) {
return filterOption(filter, item);
}
return text.indexOf(filter) >= 0;
}
render() {
const { render, filter, item, lazy, checked, prefixCls, onClick,renderedText,renderedEl } = this.props;
const className = classNames({
[`${prefixCls}-content-item`]: true,
[`${prefixCls}-content-item-disabled`]: item.disabled,
});
const lazyProps = assign({
height: 32,
offset: 500,
throttle: 0,
debounce: false,
}, lazy);
let lazyFlag = true;
if(lazy && lazy.container == "modal")
{
lazyFlag = false
}
if(!lazyFlag) {
return (
<li
className={className}
title={renderedText}
onClick={item.disabled ? undefined : () => onClick(item)}
>
<Checkbox checked={checked} disabled={item.disabled} onClick={item.disabled ? undefined : () => onClick(item)}/>
<span>{renderedEl}</span>
</li>
)
}else {
return (
<Lazyload {...lazyProps}>
<li
className={className}
title={renderedText}
onClick={item.disabled ? undefined : () => onClick(item)}
>
<Checkbox checked={checked} disabled={item.disabled} onClick={item.disabled ? undefined : () => onClick(item)}/>
<span>{renderedEl}</span>
</li>
</Lazyload>
);
}
}
}
export default Item;
import React from 'react';
import classNames from 'classnames';
import PureRenderMixin from './PureRenderMixin';
import assign from 'object-assign';
import Lazyload from 'react-lazy-load';
import Checkbox from 'bee-checkbox';
function isRenderResultPlainObject(result) {
return result && !React.isValidElement(result) &&
Object.prototype.toString.call(result) === '[object Object]';
}
class Item extends React.Component{
shouldComponentUpdate(...args) {
return PureRenderMixin.shouldComponentUpdate.apply(this, args);
}
// matchFilter = (text) => {
// const { filter, filterOption, item } = this.props;
// if (filterOption) {
// return filterOption(filter, item);
// }
// return text.indexOf(filter) >= 0;
// }
render() {
const { render, filter, item, lazy, checked, prefixCls, onClick,renderedText,renderedEl, showCheckbox } = this.props;
const className = classNames({
[`${prefixCls}-content-item`]: true,
[`${prefixCls}-content-item-disabled`]: item.disabled,
[`${prefixCls}-content-item-selected`]: checked
});
const lazyProps = assign({
height: 32,
offset: 500,
throttle: 0,
debounce: false,
}, lazy);
let lazyFlag = true;
if(lazy && lazy.container == "modal")
{
lazyFlag = false
}
if(!lazyFlag) {
return (
<li
className={className}
title={renderedText}
onClick={item.disabled ? undefined : () => onClick(item)}
>
<Checkbox checked={checked} disabled={item.disabled} onClick={item.disabled ? undefined : () => onClick(item)}/>
<span>{renderedEl}</span>
</li>
)
}else {
return (
<Lazyload {...lazyProps}>
<li
className={className}
title={renderedText}
onClick={item.disabled ? undefined : () => onClick(item)}
>
{
showCheckbox?
<Checkbox checked={checked} disabled={item.disabled} onClick={item.disabled ? undefined : () => onClick(item)}/>
:''
}
<span>{renderedEl}</span>
</li>
</Lazyload>
);
}
}
}
export default Item;

View File

@ -1,234 +1,303 @@
import React from 'react';
import Search from './search';
import classNames from 'classnames';
import Animate from 'bee-animate';
import PureRenderMixin from './PureRenderMixin';
import assign from 'object-assign';
import { TransferItem } from './index';
import Item from './item';
import Checkbox from 'bee-checkbox';
function noop() {
}
const defaultProps = {
dataSource: [],
titleText: '',
showSearch: false,
render: noop,
};
function isRenderResultPlainObject(result) {
return result && !React.isValidElement(result) &&
Object.prototype.toString.call(result) === '[object Object]';
}
class TransferList extends React.Component {
constructor(props) {
super(props);
this.state = {
mounted: false,
};
}
componentDidMount() {
this.timer = setTimeout(() => {
this.setState({
mounted: true,
});
}, 0);
}
componentWillUnmount() {
clearTimeout(this.timer);
}
shouldComponentUpdate(...args) {
return PureRenderMixin.shouldComponentUpdate.apply(this, args);
}
matchFilter = (text,item) => {
const { filter, filterOption} = this.props;
if (filterOption) {
return filterOption(filter, item);
}
return text.indexOf(filter) >= 0;
}
getCheckStatus(filteredDataSource) {
const { checkedKeys } = this.props;
if (checkedKeys.length === 0) {
return 'none';
} else if (filteredDataSource.every(item => checkedKeys.indexOf(item.key) >= 0)) {
return 'all';
}
return 'part';
}
handleSelect = (selectedItem) => {
const { checkedKeys } = this.props;
const result = checkedKeys.some((key) => key === selectedItem.key);
this.props.handleSelect(selectedItem, !result);
}
handleFilter = (e) => {
this.props.handleFilter(e);
}
handleClear = () => {
this.props.handleClear();
}
renderItem = (item) => {
const { render = noop } = this.props;
const renderResult = render(item);
const isRenderResultPlain = isRenderResultPlainObject(renderResult);
return {
renderedText: isRenderResultPlain ? renderResult.value : renderResult,
renderedEl: isRenderResultPlain ? renderResult.label : renderResult,
};
}
renderCheckbox({ prefixCls, filteredDataSource, checked, checkPart, disabled, checkable }) {
const checkAll = (!checkPart) && checked;
prefixCls = "u"
const checkboxCls = classNames({
[`${prefixCls}-checkbox-indeterminate`]: checkPart,
[`${prefixCls}-checkbox-disabled`]: disabled,
});
return (
<span
className="u-checkbox-wrapper"
>
<Checkbox
onChange={() => this.props.handleSelectAll(filteredDataSource, checkAll)}
className={checkboxCls}
checked={checkAll}
/>
</span>
);
}
render() {
const { prefixCls, dataSource, titleText, filter, checkedKeys, lazy, filterOption,
body = noop, footer = noop, showSearch, render = noop, style } = this.props;
let { searchPlaceholder, notFoundContent } = this.props;
// Custom Layout
const footerDom = footer(assign({}, this.props));
const bodyDom = body(assign({}, this.props));
const listCls = classNames(prefixCls, {
[`${prefixCls}-with-footer`]: !!footerDom,
});
const filteredDataSource = [];
const totalDataSource = [];
const showItems = dataSource.map((item) => {
const { renderedText, renderedEl } = this.renderItem(item);
if (filter && filter.trim() && !this.matchFilter(renderedText, item)) {
return null;
}
// all show items
totalDataSource.push(item);
if (!item.disabled) {
filteredDataSource.push(item);
}
const checked = checkedKeys.indexOf(item.key) >= 0;
return (
<Item
key={item.key}
item={item}
lazy={lazy}
render={render}
renderedText={renderedText}
renderedEl={renderedEl}
filter={filter}
filterOption={filterOption}
checked={checked}
prefixCls={prefixCls}
onClick={this.handleSelect}
/>
);
});
let unit = '';
const antLocale = this.context.antLocale;
if (antLocale && antLocale.Transfer) {
const transferLocale = antLocale.Transfer;
unit = dataSource.length > 1 ? transferLocale.itemsUnit : transferLocale.itemUnit;
searchPlaceholder = searchPlaceholder || transferLocale.searchPlaceholder;
notFoundContent = notFoundContent || transferLocale.notFoundContent;
}
const checkStatus = this.getCheckStatus(filteredDataSource);
const outerPrefixCls = prefixCls.replace('-list', '');
const search = showSearch ? (
<div className={`${prefixCls}-body-search-wrapper`}>
<Search
prefixCls={`${prefixCls}-search`}
onChange={this.handleFilter}
handleClear={this.handleClear}
placeholder={searchPlaceholder || 'Search'}
value={filter}
/>
</div>
) : null;
const listBody = bodyDom || (
<div className={showSearch ? `${prefixCls}-body ${prefixCls}-body-with-search` : `${prefixCls}-body`}>
{search}
<Animate
component="ul"
className={`${prefixCls}-content`}
transitionName={this.state.mounted ? `${prefixCls}-content-item-highlight` : ''}
transitionLeave={false}
>
{showItems}
</Animate>
<div className={`${prefixCls}-body-not-found`}>
{notFoundContent || 'Not Found'}
</div>
</div>
);
const listFooter = footerDom ? (
<div className={`${prefixCls}-footer`}>
{footerDom}
</div>
) : null;
const renderedCheckbox = this.renderCheckbox({
prefixCls: outerPrefixCls,
checked: checkStatus === 'all',
checkPart: checkStatus === 'part',
checkable: <span className={`${outerPrefixCls}-checkbox-inner`} />,
filteredDataSource,
disabled: false,
});
return (
<div className={listCls} style={style}>
<div className={`${prefixCls}-header`}>
{renderedCheckbox}
<span className={`${prefixCls}-header-selected`}>
<span>
{(checkedKeys.length > 0 ? `${checkedKeys.length}/` : '') + totalDataSource.length} {unit}
</span>
<span className={`${prefixCls}-header-title`}>
{titleText}
</span>
</span>
</div>
{listBody}
{listFooter}
</div>
);
}
}
TransferList.defaultProps = defaultProps;
import React from 'react';
import Search from './search';
import classNames from 'classnames';
import Animate from 'bee-animate';
import PureRenderMixin from './PureRenderMixin';
import assign from 'object-assign';
import { TransferItem } from './index';
import Item from './item';
import Checkbox from 'bee-checkbox';
import Icon from 'bee-icon';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import { KeyCode} from 'tinper-bee-core';
function noop() {
}
const defaultProps = {
dataSource: [],
titleText: '',
showSearch: false,
render: noop,
};
function isRenderResultPlainObject(result) {
return result && !React.isValidElement(result) &&
Object.prototype.toString.call(result) === '[object Object]';
}
class TransferList extends React.Component {
constructor(props) {
super(props);
this.state = {
mounted: false,
};
}
componentDidMount() {
this.timer = setTimeout(() => {
this.setState({
mounted: true,
});
}, 0);
}
componentWillUnmount() {
clearTimeout(this.timer);
}
shouldComponentUpdate(...args) {
return PureRenderMixin.shouldComponentUpdate.apply(this, args);
}
matchFilter = (text,item) => {
//filter搜索框中的内容
//filterOption用户自定义的搜索过滤方法
const { filter, filterOption} = this.props;
if (filterOption) {
return filterOption(filter, item);
}
return text.indexOf(filter) >= 0;
}
/**
* 获取Checkbox状态
* @param {*} filteredDataSource dataSource中刨去设置为disabled的部分
*/
getCheckStatus(filteredDataSource) {
const { checkedKeys } = this.props;
if (checkedKeys.length === 0) {
return 'none'; //全部未选
} else if (filteredDataSource.every(item => checkedKeys.indexOf(item.key) >= 0)) {
return 'all'; //全部已选
}
return 'part'; //部分已选
}
/**
* 点击list item选中或取消选中
* @param selectedItem 选中的item的信息和dataSource数据源中的item信息一致
*/
handleSelect = (selectedItem) => {
// checkedKeys已勾选的Keys数组
// result是否已勾选true已勾选 false未勾选
const { checkedKeys } = this.props;
const result = checkedKeys.some((key) => key === selectedItem.key);
this.props.handleSelect(selectedItem, result);
}
handleFilter = (e) => {
this.props.handleFilter(e);
}
handleClear = () => {
this.props.handleClear();
}
renderItem = (item) => {
const { render = noop } = this.props;
const renderResult = render(item);
const isRenderResultPlain = isRenderResultPlainObject(renderResult);
return {
renderedText: isRenderResultPlain ? renderResult.value : renderResult,
renderedEl: isRenderResultPlain ? renderResult.label : renderResult,
};
}
renderCheckbox({ prefixCls, filteredDataSource, checked, checkPart, disabled, checkable }) {
const checkAll = (!checkPart) && checked; //非半选 && 全选
prefixCls = "u"
const checkboxCls = classNames({
[`${prefixCls}-checkbox-indeterminate`]: checkPart,
[`${prefixCls}-checkbox-disabled`]: disabled,
});
return (
<span
className="u-checkbox-wrapper"
>
<Checkbox
onChange={() => this.props.handleSelectAll(filteredDataSource, checkAll)}
className={checkboxCls}
checked={checkAll}
/>
</span>
);
}
onKeyDown = (event,provided,snapshot,item) => {
if (provided.dragHandleProps) {
provided.dragHandleProps.onKeyDown(event);
}
if (event.defaultPrevented) {
return;
}
if (snapshot.isDragging) {
return;
}
if (event.keyCode !== KeyCode.ENTER) {
return;
}
// 为了选择,我们使用此事件 we are using the event for selection
event.preventDefault();
this.performAction(event,item);
};
render() {
const { prefixCls, dataSource, titleText, filter, checkedKeys, lazy, filterOption,
body = noop, footer = noop, showSearch, render = noop, style, id, showCheckbox, draggable } = this.props;
let { searchPlaceholder, notFoundContent } = this.props;
// Custom Layout
const footerDom = footer(assign({}, this.props));
const bodyDom = body(assign({}, this.props));
const listCls = classNames(prefixCls, {
[`${prefixCls}-with-footer`]: !!footerDom,
[`${prefixCls}-draggable`]: !!draggable
});
const filteredDataSource = [];
const totalDataSource = [];
const showItems = dataSource.map((item,index) => {
const { renderedText, renderedEl } = this.renderItem(item);
if (filter && filter.trim() && !this.matchFilter(renderedText, item)) {
return null;
}
// all show items
totalDataSource.push(item);
if (!item.disabled) {
filteredDataSource.push(item);
}
const checked = checkedKeys.indexOf(item.key) >= 0;
return (
<Draggable key={item.key} index={index} draggableId={`${item.key}`} isDragDisabled={draggable ? item.disabled : !draggable}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
// onClick={(event) =>this.handleDrag(event, provided, snapshot, item)}
onKeyDown={(event) =>
this.onKeyDown(event, provided, snapshot, item)
}
// className={classnames({
// ...getClass(this.props,snapshot.isDragging).drag
// })}
style={{...provided.draggableProps.style}}>
<Item
// ref={provided.innerRef} //Error: provided.innerRef has not been provided with a HTMLElement
// key={item.key}
item={item}
lazy={lazy}
render={render}
renderedText={renderedText}
renderedEl={renderedEl}
filter={filter}
filterOption={filterOption}
checked={checked}
prefixCls={prefixCls}
onClick={this.handleSelect}
showCheckbox={showCheckbox}
/>
</div>
)}
</Draggable>)
});
let unit = '';
const antLocale = this.context.antLocale;
if (antLocale && antLocale.Transfer) {
const transferLocale = antLocale.Transfer;
unit = dataSource.length > 1 ? transferLocale.itemsUnit : transferLocale.itemUnit;
searchPlaceholder = searchPlaceholder || transferLocale.searchPlaceholder;
notFoundContent = notFoundContent || transferLocale.notFoundContent;
}
const checkStatus = this.getCheckStatus(filteredDataSource);
const outerPrefixCls = prefixCls.replace('-list', '');
const search = showSearch ? (
<div className={`${prefixCls}-body-search-wrapper`}>
<Search
prefixCls={`${prefixCls}-search`}
onChange={this.handleFilter}
handleClear={this.handleClear}
placeholder={searchPlaceholder}
value={filter}
/>
</div>
) : null;
const listBody = bodyDom || (
<div className={showSearch ? `${prefixCls}-body ${prefixCls}-body-with-search` : `${prefixCls}-body`}>
{search}
<Droppable droppableId={`droppable_${id}`} direction='vertical' isDropDisabled={!draggable}>
{(provided, snapshot) => (
<div ref={provided.innerRef} isDraggingOver={snapshot.isDraggingOver} key={id} className={`${prefixCls}-content`}>
<Animate
component="ul"
transitionName={this.state.mounted ? `${prefixCls}-content-item-highlight` : ''}
transitionLeave={false}
>
{showItems}
</Animate>
<div className={`${prefixCls}-delete-selected ${snapshot.isDraggingOver && id === '1'? 'show': ''}`}>
<div className={`${prefixCls}-del-btn`}>
<Icon type="uf-arrow-down-2"></Icon>
<span>移除已选</span>
</div>
</div>
</div>
)}
</Droppable>
<div className={`${prefixCls}-body-not-found ${dataSource.length == 0? "show" : ""}`}>
{notFoundContent}
</div>
</div>
);
const listFooter = footerDom ? (
<div className={`${prefixCls}-footer`}>
{footerDom}
</div>
) : null;
const renderedCheckbox = this.renderCheckbox({
prefixCls: outerPrefixCls,
checked: checkStatus === 'all',
checkPart: checkStatus === 'part',
checkable: <span className={`${outerPrefixCls}-checkbox-inner`} />,
filteredDataSource,
disabled: false,
});
return (
<div className={listCls} style={style}>
<div className={`${prefixCls}-header`}>
{showCheckbox ? renderedCheckbox : ''}
<span className={`${prefixCls}-header-selected`}>
<span>
{(checkedKeys.length > 0 ? `${checkedKeys.length}/` : '') + totalDataSource.length} {unit}
</span>
<span className={`${prefixCls}-header-title`}>
{titleText}
</span>
</span>
</div>
{listBody}
{listFooter}
</div>
);
}
}
TransferList.defaultProps = defaultProps;
export default TransferList;

View File

@ -1,60 +1,60 @@
import React from 'react';
import Button from 'bee-button';
import Icon from 'bee-icon';
import PropTypes from 'prop-types';
function noop() {
}
const propTypes = {
className: PropTypes.string,
leftArrowText: PropTypes.string,
rightArrowText: PropTypes.string,
moveToLeft: PropTypes.func,
moveToRight: PropTypes.func,
leftActive: PropTypes.boolean,
rightActive: PropTypes.boolean,
}
const defaultProps = {
leftArrowText: '',
rightArrowText: '',
moveToLeft: noop,
moveToRight: noop,
};
class TransferOperation extends React.Component{
render() {
const {
moveToLeft,
moveToRight,
leftArrowText,
rightArrowText,
leftActive,
rightActive,
className,
} = this.props;
const moveToLeftButton = (
<Button size="sm" disabled={!leftActive} onClick={moveToLeft}>
{<span><Icon type="uf-arrow-left" />{leftArrowText}</span>}
</Button>
);
const moveToRightButton = (
<Button size="sm" disabled={!rightActive} onClick={moveToRight}>
{<span>{rightArrowText}<Icon type="uf-arrow-right" /></span>}
</Button>
);
return (
<div className={className}>
{moveToLeftButton}
{moveToRightButton}
</div>
);
}
}
TransferOperation.propsType = propTypes;
TransferOperation.defaultProps = defaultProps;
import React from 'react';
import Button from 'bee-button';
import Icon from 'bee-icon';
import PropTypes from 'prop-types';
function noop() {
}
const propTypes = {
className: PropTypes.string,
leftArrowText: PropTypes.string,
rightArrowText: PropTypes.string,
moveToLeft: PropTypes.func,
moveToRight: PropTypes.func,
leftActive: PropTypes.boolean,
rightActive: PropTypes.boolean,
}
const defaultProps = {
leftArrowText: '',
rightArrowText: '',
moveToLeft: noop,
moveToRight: noop,
};
class TransferOperation extends React.Component{
render() {
const {
moveToLeft,
moveToRight,
leftArrowText,
rightArrowText,
leftActive,
rightActive,
className,
} = this.props;
const moveToLeftButton = (
<Button size="sm" disabled={!leftActive} onClick={moveToLeft}>
{<span><Icon type="uf-arrow-left" />{leftArrowText}</span>}
</Button>
);
const moveToRightButton = (
<Button size="sm" disabled={!rightActive} onClick={moveToRight}>
{<span>{rightArrowText}<Icon type="uf-arrow-right" /></span>}
</Button>
);
return (
<div className={className}>
{moveToLeftButton}
{moveToRightButton}
</div>
);
}
}
TransferOperation.propsType = propTypes;
TransferOperation.defaultProps = defaultProps;
export default TransferOperation;

View File

@ -44,6 +44,7 @@ class Search extends React.Component {
return (
<div>
<FormControl
size="sm"
placeholder={placeholder}
className={prefixCls}
value={value}

47
src/utils.js Normal file
View File

@ -0,0 +1,47 @@
/**
* a little function to help us with reordering the result
* @param {*} list
* @param {*} targetKeys
* @param {*} startIndex
* @param {*} endIndex
*/
const reorder = (list,targetKeys, startIndex, endIndex) => {
const result1 = Array.from(list);
const [removed1] = result1.splice(startIndex, 1);
result1.splice(endIndex, 0, removed1);
const result2 = Array.from(targetKeys);
const [removed2] = result2.splice(startIndex, 1);
result2.splice(endIndex, 0, removed2);
let result = {};
result.dataArr = result1;
result.targetKeyArr = result2;
return result;
};
/**
* Moves an item from one list to another list.
* @param {*} source
* @param {*} destination
* @param {*} droppableSource
* @param {*} droppableDestination
* @param {*} targetKeys
*/
const move = (source, destination, droppableSource, droppableDestination, targetKeys) => {
const sourceClone = Array.from(source);
const destClone = Array.from(destination);
const [removed] = sourceClone.splice(droppableSource.index, 1);
destClone.splice(droppableDestination.index, 0, removed);
targetKeys.splice(droppableDestination.index, 0, removed.key);
const result = {};
result[droppableSource.droppableId] = sourceClone;
result[droppableDestination.droppableId] = destClone;
result.newTargetKeys = targetKeys;
return result;
};
export { reorder, move }