sunmao-sdk
Version:
榫卯-开箱即用赋能-sdk
403 lines (378 loc) • 13.6 kB
JSX
/**
* Created by Tw93 on 2019-12-01.
* 抽离高阶列表组件
*/
import React from "react";
import PropTypes from "prop-types";
import {
SortableContainer,
SortableHandle,
SortableElement,
arrayMove
} from "react-sortable-hoc";
import { isFunction, evaluateString, isObj, isNumber } from "../base/utils";
import FoldIcon from "./foldIcon";
import DescriptionList, { getDescription } from "./descList";
const DragHandle = SortableHandle(() => (
<div className="fr-item-action-icon fr-move-icon">:::</div>
));
const listItemHoc = ButtonComponent =>
class extends React.Component {
componentDidMount() {
const { p = {}, name, fold } = this.props;
const description = getDescription({
schema: p.schema,
value: p.value,
index: name
});
// 如果第一个值不为空,则收起
// 新增的值为0,不折叠
const hasValue = description && description[0] && description[0].text;
if (hasValue && fold !== 0) {
this.props.toggleFoldItem(name);
}
}
toggleFold = () => {
this.props.toggleFoldItem(this.props.name);
};
handleDelete = () => {
const { p = {}, name, pageSize } = this.props;
const value = [...p.value];
value.splice(name, 1);
this.props.handleDeleteItem(name);
p.onChange(p.name, value);
// 计算页码
const list = Array.isArray(value) ? value : [];
const page = Math.ceil(list.length / pageSize);
this.props.handlePageChange(page, pageSize);
};
render() {
const { item, p = {}, name, fold } = this.props;
const descProps = { ...p, index: name };
const { options, readOnly, formData, value: rootValue } = p;
const _options = isObj(options) ? options : {};
const { foldable: canFold, hideIndex } = _options;
let { hideDelete, itemButtons } = _options;
// 判断 hideDelete 是不是函数,是的话将计算后的值赋回
let _isFunction = isFunction(hideDelete);
if (_isFunction) {
// isFunction 返回为 true 则说明只可能为 string | Function
if (typeof _isFunction === "string") {
hideDelete = evaluateString(_isFunction, formData, rootValue);
} else if (typeof _isFunction === "function") {
hideDelete = _isFunction(formData, rootValue);
}
}
// 只有当items为object时才做收起(fold)处理
const isSchemaObj = p.schema.items && p.schema.items.type == "object";
let setClass =
"fr-set ba b--black-10 hover-b--black-20 relative flex flex-column";
if (canFold && fold) {
setClass += " pv12";
} else if (p.displayType === "row") {
setClass += " pt44";
}
return (
<li className={setClass}>
{hideIndex ? null : (
<div
className="absolute top-0 left-0 bg-blue"
style={{
paddingLeft: 4,
paddingRight: 6,
borderBottomRightRadius: 8,
borderTopLeftRadius: 3,
backgroundColor: "rgba(0, 0, 0, .36)",
fontSize: 8,
color: "#fff"
}}
>
{name + 1}
</div>
)}
{canFold && fold && isSchemaObj ? (
<DescriptionList {...descProps} />
) : (
item
)}
<div className="fr-item-actions">
{canFold && (
<FoldIcon
fold={fold}
onClick={this.toggleFold}
className="fr-item-action-icon"
/>
)}
{!readOnly && (
<div className="fr-item-action-icon" onClick={this.handleDelete}>
<img
style={{ height: "70%" }}
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACABAMAAAAxEHz4AAAAFVBMVEVHcEwyMjIzMzMzMzMzMzMyMjIzMzPB9FYmAAAABnRSTlMAwO5OlCWVPuLSAAABQklEQVRo3u2ZzQ6CMBCEQeSuHHrGn3gmRjkbjZxN9AVA0/d/BAPR2OKCU+pJZ04E2A+6hTY7GwQU1atNoh+aHYbEh9rQZABAW3KPH9uAnTMgtwFXZ0BhA27OAGUDSmfAygZUvTdHiR6gMutKGKpr13jhV+j4ZmBV3wN4D8E7id7T+MyDy4+KnSSAAAJ+ARAlrwXDPIYBubFk5a3NFQIoY9FUrb0RApjLdntvRAEauUYAAQQQQAABBBBAAAEE/CXAr+DwLnm8iy7vso+1838CaiP6hMXHolFdAzIMEIkAhfcAxqLXXrv5CwywFt3+HLfwldhvGMF9jKb3kcqnS2AeYiU/Km4aCsvtp/jzvnHFhScVLqa01DEJXQAT6eVWQ3z9t3nAlMr5KXwy0MwkOIiq83O5QITq2POfTeefwufTLKCotu7zGChb6PfVgwAAAABJRU5ErkJggg=="
alt="delete"
/>
</div>
)}
{!readOnly && <DragHandle />}
</div>
{!((canFold && fold) || hideDelete || readOnly) && (
<div className="self-end flex mb2">
{itemButtons &&
itemButtons.length > 0 &&
itemButtons.map((btn, idx) => {
return (
<ButtonComponent
key={idx.toString()}
className="ml2"
type="dashed"
icon={btn.icon}
onClick={() => {
const value = [...p.value];
if (typeof window[btn.callback] === "function") {
const result = window[btn.callback](value, name); // eslint-disable-line
p.onChange(p.name, result);
}
}}
>
{btn.text || ""}
</ButtonComponent>
);
})}
</div>
)}
</li>
);
}
};
const fieldListHoc = (ButtonComponent, Pagination) => {
const SortableItem = SortableElement(listItemHoc(ButtonComponent));
return class extends React.Component {
handleAddClick = () => {
const { p, addUnfoldItem, pageSize } = this.props;
const value = [...p.value];
value.push(p.newItem);
p.onChange(p.name, value);
addUnfoldItem();
// 计算页码
const list = Array.isArray(value) ? value : [];
const page = Math.ceil(list.length / pageSize);
this.props.handlePageChange(page, pageSize);
};
onPageChange = (page, pageSize) => {
this.props.handlePageChange(page, pageSize);
};
// buttons is a list, each item looks like:
// {
// "text": "删除全部",
// "icon": "delete",
// "callback": "clearAll"
// }
render() {
const {
p,
foldList = [],
currentIndex,
pageSize,
handlePageChange,
toggleFoldItem,
handleDeleteItem
} = this.props;
// prefer ui:options/buttons to ui:extraButtons, but keep both for backwards compatibility
const { readOnly, schema = {}, extraButtons, options } = p || {};
const _options = isObj(options) ? options : {};
const buttons = _options.buttons || extraButtons || [];
const { maxItems } = schema;
const list = Array.isArray(p.value) ? p.value : [];
const total = list.length;
const currentPage = list.slice(
(currentIndex - 1) * pageSize,
currentIndex * pageSize
);
if (!Array.isArray(p.value)) {
console.error(`"${p.name}"这个字段相关的schema书写错误,请检查!`);
return null;
}
const canAdd = maxItems ? maxItems > list.length : true; // 当到达最大个数,新增按钮消失
const showPagination = !!(Pagination && total > pageSize);
return (
<ul className="pl0 ma0">
{currentPage.map((_, idx) => {
const name = (currentIndex - 1) * pageSize + idx;
return (
<SortableItem
key={`item-${name}`}
index={name}
name={name}
p={p}
fold={foldList[name]}
toggleFoldItem={toggleFoldItem}
pageSize={pageSize}
handlePageChange={handlePageChange}
handleDeleteItem={handleDeleteItem}
item={p.getSubField({
name,
value: p.value[name],
onChange(key, val) {
const value = [...p.value];
value[key] = val;
p.onChange(p.name, value);
}
})}
/>
);
})}
<div className="flex justify-between mb3">
{showPagination ? (
<Pagination
size="small"
total={total}
pageSize={pageSize}
showSizeChanger={showPagination}
onChange={this.onPageChange}
current={currentIndex}
/>
) : (
<div />
)}
{!readOnly && (
<div className="tr">
{canAdd && (
<ButtonComponent icon="add" onClick={this.handleAddClick}>
新增
</ButtonComponent>
)}
{buttons &&
buttons.length > 0 &&
buttons.map((item, i) => {
const { icon, text, callback, ...rest } = item;
return (
<ButtonComponent
className="ml2"
icon={icon}
key={i.toString()}
onClick={() => {
if (callback === "clearAll") {
p.onChange(p.name, []);
return;
}
if (callback === "copyLast") {
const value = [...p.value];
const lastIndex = value.length - 1;
value.push(
lastIndex > -1 ? value[lastIndex] : p.newItem
);
p.onChange(p.name, value);
return;
}
if (typeof window[callback] === "function") {
const value = [...p.value];
const onChange = value => p.onChange(p.name, value);
window[callback](
value,
onChange,
schema,
p.newItem
); // eslint-disable-line
}
}}
{...rest}
>
{text}
</ButtonComponent>
);
})}
</div>
)}
</div>
</ul>
);
}
};
};
export default function listHoc(ButtonComponent, Pagination) {
const SortableList = SortableContainer(
fieldListHoc(ButtonComponent, Pagination)
);
return class extends React.Component {
static propTypes = {
value: PropTypes.array
};
static defaultProps = {
value: []
};
constructor(props) {
super(props);
const len = this.props.value.length || 0;
this.state = {
foldList: new Array(len).fill(false) || [],
currentIndex: 1,
pageSize: this.getPageSize(props)
};
}
// 新添加的item默认是展开的
addUnfoldItem = () => {
this.setState({
foldList: [...this.state.foldList, 0]
});
};
handleDeleteItem = () => {
const { options, value } = this.props;
let pageSize = 10;
if (isNumber(options.pageSize)) {
pageSize = Number(options.pageSize);
}
const idx =
Math.floor((value.length === 0 ? 0 : value.length - 2) / pageSize) + 1;
if (this.state.currentIndex > idx) {
this.setState({ currentIndex: Math.max(idx, 1) });
}
};
toggleFoldItem = index => {
const { foldList = [] } = this.state;
foldList[index] = !foldList[index]; // TODO: need better solution for the weird behavior caused by setState being async
this.setState({
foldList
});
};
handleSort = ({ oldIndex, newIndex }) => {
const { onChange, name, value } = this.props;
onChange(name, arrayMove(value, oldIndex, newIndex));
this.setState({
foldList: arrayMove(this.state.foldList, oldIndex, newIndex)
});
};
handlePageChange = (page, pageSize) => {
this.setState({ currentIndex: Math.max(page, 1), pageSize });
};
getPageSize = props => {
const { options } = props || {};
const _options = isObj(options) ? options : {};
let pageSize = 10;
if (isNumber(_options.pageSize)) {
pageSize = Number(_options.pageSize);
}
return pageSize;
};
render() {
const { foldList, currentIndex, pageSize } = this.state;
return (
<SortableList
p={this.props}
foldList={foldList}
currentIndex={currentIndex}
pageSize={pageSize}
toggleFoldItem={this.toggleFoldItem}
addUnfoldItem={this.addUnfoldItem}
handlePageChange={this.handlePageChange}
handleDeleteItem={this.handleDeleteItem}
distance={6}
useDragHandle
helperClass="fr-sort-help-class"
shouldCancelStart={e =>
e.toElement && e.toElement.className === "fr-tooltip-container"
}
onSortEnd={this.handleSort}
/>
);
}
};
}