mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-02 20:18:03 +08:00
commit
334e80cddc
@ -1,3 +1,33 @@
|
||||
@mixin tree-input {
|
||||
> svg {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
width: px2rem(16px);
|
||||
height: px2rem(16px);
|
||||
margin-left: px2rem(5px);
|
||||
}
|
||||
|
||||
> input {
|
||||
margin-left: px2rem(15px);
|
||||
padding: px2rem(5px);
|
||||
width: px2rem(150px);
|
||||
height: px2rem(25px);
|
||||
color: $Form-input-color;
|
||||
|
||||
&::placeholder {
|
||||
color: $Form-input-placeholderColor;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: $borderWidth solid $info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo
|
||||
.#{$ns}TreeControl {
|
||||
border: 1px solid $Form-input-borderColor;
|
||||
@ -29,27 +59,79 @@
|
||||
&.is-folded {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> li {
|
||||
@include tree-input;
|
||||
}
|
||||
}
|
||||
|
||||
&-item {
|
||||
line-height: px2rem(30px);
|
||||
position: relative;
|
||||
|
||||
.#{$ns}Tree-item-icons {
|
||||
visibility: hidden;
|
||||
transition: visibility .1s ease;
|
||||
}
|
||||
|
||||
> a {
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
|
||||
> span.#{$ns}Tree-item-icons {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
> span > svg {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
width: px2rem(16px);
|
||||
height: px2rem(16px);
|
||||
margin-left: px2rem(5px);
|
||||
}
|
||||
}
|
||||
|
||||
&--isLeaf > a {
|
||||
padding-left: $Tree-itemArrowWidth + $gap-xs;
|
||||
}
|
||||
|
||||
&--isEdit {
|
||||
display: inline-block;
|
||||
@include tree-input;
|
||||
}
|
||||
}
|
||||
|
||||
&-rootItem > a > i {
|
||||
margin-left: 0 !important;
|
||||
&-rootItem {
|
||||
> a > i {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.#{$ns}Tree-addTop {
|
||||
height: px2rem(25px);
|
||||
line-height: px2rem(25px);
|
||||
cursor: pointer;
|
||||
padding-left: $Tree-indent;
|
||||
> p {
|
||||
> svg {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
width: px2rem(16px);
|
||||
height: px2rem(16px);
|
||||
}
|
||||
> span {
|
||||
padding-left: px2rem(5px);
|
||||
}
|
||||
}
|
||||
|
||||
&-input {
|
||||
@include tree-input
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-itemArrow {
|
||||
|
@ -5,16 +5,17 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {eachTree, isVisible} from '../utils/helper';
|
||||
import {eachTree, isVisible, isObject, autobind} from '../utils/helper';
|
||||
import {Option, Options, value2array} from './Checkboxes';
|
||||
import {ClassNamesFn, themeable} from '../theme';
|
||||
import {highlight} from '../renderers/Form/Options';
|
||||
import {Icon} from './icons';
|
||||
|
||||
interface TreeSelectorProps {
|
||||
classPrefix: string;
|
||||
classnames: ClassNamesFn;
|
||||
|
||||
highlightTxt: string;
|
||||
highlightTxt?: string;
|
||||
|
||||
showIcon?: boolean;
|
||||
// 是否默认都展开
|
||||
@ -53,11 +54,26 @@ interface TreeSelectorProps {
|
||||
selfDisabledAffectChildren?: boolean;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
addMode?: 'dialog' | 'normal';
|
||||
addable?: boolean;
|
||||
onAdd?: Function;
|
||||
openAddDialog?: Function;
|
||||
editMode?: 'dialog' | 'normal';
|
||||
onEdit?: Function;
|
||||
editable?: boolean;
|
||||
openEditDialog?: Function;
|
||||
deletable?: boolean;
|
||||
onRemove?: Function;
|
||||
}
|
||||
|
||||
interface TreeSelectorState {
|
||||
value: Array<any>;
|
||||
unfolded: {[propName: string]: string};
|
||||
editItem: Option | null;
|
||||
addItem: Option | null;
|
||||
addingItem: Option | null;
|
||||
editingItem: Option | null;
|
||||
addTop: boolean;
|
||||
}
|
||||
|
||||
export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelectorState> {
|
||||
@ -87,12 +103,6 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
this.renderList = this.renderList.bind(this);
|
||||
this.handleSelect = this.handleSelect.bind(this);
|
||||
this.clearSelect = this.clearSelect.bind(this);
|
||||
this.handleCheck = this.handleCheck.bind(this);
|
||||
this.toggleUnfolded = this.toggleUnfolded.bind(this);
|
||||
|
||||
const props = this.props;
|
||||
|
||||
this.setState({
|
||||
@ -104,7 +114,12 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
|
||||
valueField: props.valueField,
|
||||
options: props.data
|
||||
}),
|
||||
unfolded: this.syncUnFolded(props)
|
||||
unfolded: this.syncUnFolded(props),
|
||||
editItem: null, // 点击编辑时的 item
|
||||
addItem: null, // 点击添加时的 item
|
||||
addingItem: null, // 添加后的 item
|
||||
editingItem: null, // 编辑后的 item
|
||||
addTop: false // 添加一级
|
||||
});
|
||||
}
|
||||
|
||||
@ -155,8 +170,10 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
|
||||
return unfolded;
|
||||
}
|
||||
|
||||
@autobind
|
||||
toggleUnfolded(node: any) {
|
||||
this.setState({
|
||||
addItem: null,
|
||||
unfolded: {
|
||||
...this.state.unfolded,
|
||||
[node[this.props.valueField as string]]: !this.state.unfolded[node[this.props.valueField as string]]
|
||||
@ -164,6 +181,7 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
clearSelect() {
|
||||
this.setState(
|
||||
{
|
||||
@ -177,6 +195,7 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
|
||||
);
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleSelect(node: any, value?: any) {
|
||||
this.setState(
|
||||
{
|
||||
@ -190,6 +209,7 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
|
||||
);
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleCheck(item: any, checked: boolean) {
|
||||
const props = this.props;
|
||||
const value = this.state.value.concat();
|
||||
@ -273,6 +293,127 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
|
||||
);
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleAdd(item: Option | null, isFolder: boolean) {
|
||||
const {addMode, openAddDialog, valueField} = this.props;
|
||||
let {unfolded} = this.state;
|
||||
if (addMode === 'dialog') {
|
||||
openAddDialog && openAddDialog(item ? item : null)
|
||||
} else if (addMode === 'normal') {
|
||||
// item 为 null 时为添加一级
|
||||
if (item) {
|
||||
// 添加时,默认折叠的文件夹需要展开
|
||||
if (isFolder && !unfolded[item[valueField as string]]) {
|
||||
unfolded = {
|
||||
...unfolded,
|
||||
[item[valueField as string]]: !unfolded[item[valueField as string]],
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
addItem: item,
|
||||
editItem: null,
|
||||
unfolded
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
addTop: true,
|
||||
editItem: null,
|
||||
addItem: null
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleEdit(item: Option) {
|
||||
const {editMode, openEditDialog} = this.props;
|
||||
const {addItem} = this.state;
|
||||
if (editMode === 'dialog') {
|
||||
openEditDialog && openEditDialog(item);
|
||||
addItem && this.setState({
|
||||
addItem: null
|
||||
});
|
||||
} else if (editMode === 'normal') {
|
||||
this.setState({
|
||||
editItem: item,
|
||||
addItem: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleRemove(item: Option) {
|
||||
const {onRemove} = this.props;
|
||||
onRemove && onRemove(item);
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleConfirmOnAdd() {
|
||||
const {onAdd} = this.props;
|
||||
const {addItem: parent, addingItem} = this.state;
|
||||
onAdd && onAdd({
|
||||
...addingItem,
|
||||
parent: parent
|
||||
});
|
||||
|
||||
this.setState({
|
||||
addingItem: null,
|
||||
addItem: null,
|
||||
addTop: false
|
||||
})
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleCancelOnAdd() {
|
||||
this.setState({
|
||||
addItem: null,
|
||||
addTop: false
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleConfirmOnEdit() {
|
||||
const {onEdit} = this.props;
|
||||
let {editingItem, editItem: prevItem} = this.state;
|
||||
onEdit && onEdit({
|
||||
...editingItem,
|
||||
prev: prevItem
|
||||
});
|
||||
this.setState({
|
||||
editingItem: null,
|
||||
editItem: null
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleCancelOnEdit() {
|
||||
this.setState({
|
||||
editItem: null
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleChangeOnAdd(value: string) {
|
||||
this.setState({
|
||||
addingItem: {
|
||||
label: value
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleChangeOnEdit(item: Option, value: string) {
|
||||
let {editItem} = this.state;
|
||||
this.setState({
|
||||
editingItem: {
|
||||
...item,
|
||||
label: value || (editItem as Option)['label']
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
renderList(
|
||||
list: Options,
|
||||
value: Option[],
|
||||
@ -295,8 +436,18 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
|
||||
highlightTxt,
|
||||
data,
|
||||
maxLength,
|
||||
minLength
|
||||
minLength,
|
||||
addable,
|
||||
editable,
|
||||
deletable
|
||||
} = this.props;
|
||||
const {
|
||||
addItem,
|
||||
editItem,
|
||||
unfolded,
|
||||
addTop,
|
||||
value: stateValue
|
||||
} = this.state;
|
||||
|
||||
let childrenChecked = 0;
|
||||
let ret = list.map((item, key) => {
|
||||
@ -333,8 +484,8 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
|
||||
|
||||
if (
|
||||
!nodeDisabled &&
|
||||
((maxLength && !selfChecked && this.state.value.length >= maxLength) ||
|
||||
(minLength && selfChecked && this.state.value.length <= minLength))
|
||||
((maxLength && !selfChecked && stateValue.length >= maxLength) ||
|
||||
(minLength && selfChecked && stateValue.length <= minLength))
|
||||
) {
|
||||
nodeDisabled = true;
|
||||
}
|
||||
@ -370,47 +521,70 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
|
||||
'Tree-item--isLeaf': isLeaf
|
||||
})}
|
||||
>
|
||||
<a>
|
||||
{!isLeaf ? (
|
||||
<i
|
||||
onClick={() => this.toggleUnfolded(item)}
|
||||
className={cx('Tree-itemArrow', {
|
||||
'is-folded': !this.state.unfolded[item[valueField]]
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
{!editItem || isObject(editItem) && (editItem as Option)[valueField] !== item[valueField] ? (
|
||||
<a>
|
||||
{!isLeaf ? (
|
||||
<i
|
||||
onClick={() => this.toggleUnfolded(item)}
|
||||
className={cx('Tree-itemArrow', {
|
||||
'is-folded': !unfolded[item[valueField]],
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{showIcon ? (
|
||||
<i
|
||||
className={cx(
|
||||
`Tree-itemIcon ${item[iconField] ||
|
||||
{showIcon ? (
|
||||
<i
|
||||
className={cx(
|
||||
`Tree-itemIcon ${item[iconField] ||
|
||||
(childrenItems ? 'Tree-folderIcon' : 'Tree-leafIcon')}`
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{checkbox}
|
||||
{checkbox}
|
||||
|
||||
<span
|
||||
className={cx('Tree-itemText', {
|
||||
'is-children-checked': multiple && !cascade && tmpChildrenChecked && !nodeDisabled,
|
||||
'is-checked': checked,
|
||||
'is-disabled': nodeDisabled
|
||||
})}
|
||||
onClick={() =>
|
||||
!nodeDisabled &&
|
||||
(multiple ? this.handleCheck(item, !selfChecked) : this.handleSelect(item))
|
||||
}
|
||||
>
|
||||
{highlightTxt ? highlight(item[nameField], highlightTxt) : item[nameField]}
|
||||
</span>
|
||||
</a>
|
||||
{childrenItems ? (
|
||||
<span
|
||||
className={cx('Tree-itemText', {
|
||||
'is-children-checked': multiple && !cascade && tmpChildrenChecked && !nodeDisabled,
|
||||
'is-checked': checked,
|
||||
'is-disabled': nodeDisabled,
|
||||
})}
|
||||
onClick={() =>
|
||||
!nodeDisabled &&
|
||||
(multiple ? this.handleCheck(item, !selfChecked) : this.handleSelect(item))
|
||||
}
|
||||
>
|
||||
{highlightTxt ? highlight(item[nameField], highlightTxt) : item[nameField]}
|
||||
</span>
|
||||
{!addTop
|
||||
&& !addItem
|
||||
&& !editItem ? (
|
||||
<span className={cx('Tree-item-icons')}>
|
||||
{addable ? <Icon icon="plus" className="icon" onClick={() => this.handleAdd(item, !isLeaf)}/> : null}
|
||||
{deletable ? <Icon icon="minus" className="icon" onClick={() => this.handleRemove(item)}/> : null}
|
||||
{editable ? <Icon icon="pencil" className="icon" onClick={() => this.handleEdit(item)}/> : null}
|
||||
</span>
|
||||
) : null}
|
||||
</a>
|
||||
) : (
|
||||
<div className={cx('Tree-item--isEdit')}>
|
||||
<input defaultValue={item['label']} onChange={(e) => this.handleChangeOnEdit(item, e.currentTarget.value)}/>
|
||||
<Icon icon="check" className="icon" onClick={this.handleConfirmOnEdit}/>
|
||||
<Icon icon="close" className="icon" onClick={this.handleCancelOnEdit}/>
|
||||
</div>
|
||||
)}
|
||||
{/* 有children而且为展开状态 或者 添加child时 */}
|
||||
{((childrenItems && unfolded[item[valueField]]) || addItem && (addItem[valueField] === item[valueField])) ? (
|
||||
<ul
|
||||
className={cx('Tree-sublist', {
|
||||
'is-folded': !this.state.unfolded[item[valueField]]
|
||||
})}
|
||||
className={cx('Tree-sublist')}
|
||||
>
|
||||
{addItem && addItem[valueField] === item[valueField] ? (
|
||||
<li>
|
||||
<input onChange={(e) => this.handleChangeOnAdd(e.currentTarget.value)}/>
|
||||
<Icon icon="check" className="icon" onClick={this.handleConfirmOnAdd}/>
|
||||
<Icon icon="close" className="icon" onClick={this.handleCancelOnAdd}/>
|
||||
</li>
|
||||
) : null}
|
||||
{childrenItems}
|
||||
</ul>
|
||||
) : null}
|
||||
@ -425,10 +599,10 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
|
||||
}
|
||||
|
||||
render() {
|
||||
const {className, placeholder, hideRoot, rootLabel, showIcon, classnames: cx} = this.props;
|
||||
const {className, placeholder, hideRoot, rootLabel, showIcon, classnames: cx, addable} = this.props;
|
||||
let data = this.props.data;
|
||||
const {value, addTop} = this.state;
|
||||
|
||||
const value = this.state.value;
|
||||
return (
|
||||
<div className={cx(`Tree ${className || ''}`)}>
|
||||
{data && data.length ? (
|
||||
@ -450,6 +624,23 @@ export class TreeSelector extends React.Component<TreeSelectorProps, TreeSelecto
|
||||
</span>
|
||||
</label>
|
||||
</a>
|
||||
{addable ? (
|
||||
<div className={cx('Tree-addTop')}>
|
||||
{!addTop ? (
|
||||
<p onClick={() => this.handleAdd(null, false)}>
|
||||
<Icon icon="plus" className="icon" />
|
||||
<span>添加一级</span>
|
||||
</p>
|
||||
) : null}
|
||||
{addTop ? (
|
||||
<div className={cx('Tree-addTop-input')}>
|
||||
<input onChange={(e) => this.handleChangeOnAdd(e.currentTarget.value)}/>
|
||||
<Icon icon="check" className="icon" onClick={this.handleConfirmOnAdd}/>
|
||||
<Icon icon="close" className="icon" onClick={this.handleCancelOnAdd}/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<ul className={cx('Tree-sublist')}>{this.renderList(data, value, false).dom}</ul>
|
||||
</li>
|
||||
)}
|
||||
|
@ -25,6 +25,14 @@ import PauseIcon from '../icons/pause.svg';
|
||||
import LeftArrowIcon from '../icons/left-arrow.svg';
|
||||
// @ts-ignore
|
||||
import RightArrowIcon from '../icons/right-arrow.svg';
|
||||
// @ts-ignore
|
||||
import CheckIcon from '../icons/check.svg';
|
||||
// @ts-ignore
|
||||
import PlusIcon from '../icons/plus.svg';
|
||||
// @ts-ignore
|
||||
import MinusIcon from '../icons/minus.svg';
|
||||
// @ts-ignore
|
||||
import PencilIcon from '../icons/pencil.svg';
|
||||
|
||||
// 兼容原来的用法,后续不直接试用。
|
||||
// @ts-ignore
|
||||
@ -70,6 +78,10 @@ registerIcon('play', PlayIcon);
|
||||
registerIcon('pause', PauseIcon);
|
||||
registerIcon('left-arrow', LeftArrowIcon);
|
||||
registerIcon('right-arrow', RightArrowIcon);
|
||||
registerIcon('check', CheckIcon);
|
||||
registerIcon('plus', PlusIcon);
|
||||
registerIcon('minus', MinusIcon);
|
||||
registerIcon('pencil', PencilIcon);
|
||||
|
||||
export function Icon({
|
||||
icon,
|
||||
@ -91,5 +103,9 @@ export {
|
||||
PlayIcon,
|
||||
PauseIcon,
|
||||
LeftArrowIcon,
|
||||
RightArrowIcon
|
||||
RightArrowIcon,
|
||||
CheckIcon,
|
||||
PlusIcon,
|
||||
MinusIcon,
|
||||
PencilIcon
|
||||
};
|
||||
|
3
src/icons/check.svg
Normal file
3
src/icons/check.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3506" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M972.544 175.189333a31.658667 31.658667 0 0 1 15.701333 5.162667 31.872 31.872 0 0 1 7.296 46.976c-0.682667 0.896-1.493333 1.664-2.218666 2.474667L343.082667 876.032a32.341333 32.341333 0 0 1-37.546667 5.589333 37.504 37.504 0 0 1-8.064-6.101333L30.208 597.845333c-0.768-0.810667-1.536-1.621333-2.218667-2.517333a32.256 32.256 0 0 1 14.378667-49.749333 32 32 0 0 1 28.8 3.584c2.474667 1.664 2.944 2.304 5.12 4.309333l244.736 254.250667L948.181333 184.448l2.517334-2.261333a36.693333 36.693333 0 0 1 11.861333-6.016c2.901333-0.725333 3.669333-0.725333 6.613333-1.024l3.370667 0.042666z" p-id="3507"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 759 B |
1
src/icons/minus.svg
Normal file
1
src/icons/minus.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1568167473830" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3585" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M1024 511.994q0 9-6 15-6 5-14 5-328 0-984 0-11 0-16-7-4-7-4-13 0-7 6-14 6-7 14-7 123 0 369 0 205 0 615 0 8 0 14 6 6 6 6 15z" p-id="3586"></path></svg>
|
After Width: | Height: | Size: 527 B |
3
src/icons/pencil.svg
Normal file
3
src/icons/pencil.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1208" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M302.026 783.023q-0.761 0-2.282 0.761 88.25 0 352.999 0 6.847 0 11.412 4.565 4.565 4.565 4.565 10.651 0 6.847-4.565 11.412-4.565 4.565-11.412 4.565-156.72 0-470.919 0 0 0-0.761 0 0 0-0.761 0-1.522 0-2.282-0.761 0 0-0.761 0 0 0-0.761 0 0 0 0 0 0 0-0.761-0.761-0.761 0-1.522-0.761-0.761 0-1.522-0.761 0 0-0.761-0.761-0.761-0.761-2.282-2.282-0.761-1.522-1.522-2.282-0.761-0.761-0.761-1.522 0 0 0-0.761 0-0.761-0.761-0.761 0-0.761 0-1.522 0-0.761 0-2.282 0 0 0 0 0 0 0-0.761 0 0 0-0.761 0 0 0-1.522 0 0 0-0.761 0 0 0-0.761 0 0 0.761 0 0 0 0-0.761 7.608-28.909 30.431-115.638 1.522-3.804 4.565-6.847 160.523-159.002 481.57-477.006 4.565-4.565 10.651-4.565 6.847 0 11.412 4.565 4.565 4.565 4.565 10.651 0 6.847-4.565 11.412-159.763 158.241-478.527 473.962-6.086 21.302-23.584 85.968 22.062-5.325 86.728-22.823 119.442-118.681 478.527-473.962 4.565-4.565 10.651-4.565 6.847 0 11.412 4.565 4.565 4.565 4.565 10.651 0 6.847-4.565 11.412-160.523 159.002-482.331 477.006-2.282 3.043-6.847 3.804zM823.918 269.5q0.761-0.761 3.043-3.043 3.043-3.043 6.086-6.847 0.761-0.761 3.043-3.043 22.823-22.062 22.823-53.254 0-31.192-22.823-53.254-22.062-22.062-53.254-22.062-31.192 0-53.254 22.062-4.565 4.565-12.933 12.933 26.627 26.627 107.269 106.508zM166.608 798.999q0 0 0 0.761 0 0 0-0.761zM166.608 796.717q0 0 0 1.522 0 0 0-1.522zM166.608 801.281q0 0 0-1.522 0 0 0 1.522zM167.369 795.195q-0.761 0-0.761 0.761 0 0 0-0.761 0 0 0.761 0z" p-id="1209"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
3
src/icons/plus.svg
Normal file
3
src/icons/plus.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg t="1568167179945" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2417" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M925.313 493.255c0 12.866-10.421 23.285-23.285 23.285h-349.522v349.533c0 12.853-10.421 23.285-23.285 23.285s-23.285-10.432-23.285-23.285v-349.533h-349.522c-12.866 0-23.285-10.421-23.285-23.285 0-12.853 10.421-23.285 23.285-23.285h349.522v-349.522c0-12.866 10.421-23.285 23.285-23.285s23.285 10.421 23.285 23.285v349.522h349.522c12.866 0 23.285 10.432 23.285 23.285z" p-id="2418" fill="#707070"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 586 B |
@ -2,6 +2,11 @@ import React from 'react';
|
||||
import cx from 'classnames';
|
||||
import TreeSelector from '../../components/Tree';
|
||||
import {OptionsControl, OptionsControlProps} from './Options';
|
||||
import {autobind, createObject} from '../../utils/helper';
|
||||
import {Action, Schema, PlainObject, Api, Payload} from '../../types';
|
||||
import {isEffectiveApi} from '../../utils/api';
|
||||
import {filter} from '../../utils/tpl';
|
||||
import {Option} from '../../components/Checkboxes';
|
||||
|
||||
export interface TreeProps extends OptionsControlProps {
|
||||
placeholder?: any;
|
||||
@ -12,9 +17,24 @@ export interface TreeProps extends OptionsControlProps {
|
||||
cascade?: boolean; // 父子之间是否完全独立。
|
||||
withChildren?: boolean; // 选父级的时候是否把子节点的值也包含在内。
|
||||
onlyChildren?: boolean; // 选父级的时候,是否只把子节点的值包含在内
|
||||
addApi?: Api;
|
||||
addMode?: 'dialog' | 'normal';
|
||||
addDialog?: Schema;
|
||||
editApi?: Api;
|
||||
editMode?: 'dialog' | 'normal';
|
||||
editDialog?: Schema;
|
||||
deleteApi?: Api;
|
||||
deleteConfirmText?: string;
|
||||
}
|
||||
|
||||
export default class TreeControl extends React.Component<TreeProps, any> {
|
||||
export interface TreeState {
|
||||
isAddModalOpened: boolean,
|
||||
isEditModalOpened: boolean,
|
||||
parent: Option | null,
|
||||
prev: Option | null
|
||||
}
|
||||
|
||||
export default class TreeControl extends React.Component<TreeProps, TreeState> {
|
||||
static defaultProps: Partial<TreeProps> = {
|
||||
placeholder: '选项加载中...',
|
||||
multiple: false,
|
||||
@ -24,11 +44,123 @@ export default class TreeControl extends React.Component<TreeProps, any> {
|
||||
showIcon: true
|
||||
};
|
||||
|
||||
state: TreeState = {
|
||||
isAddModalOpened: false,
|
||||
isEditModalOpened: false,
|
||||
parent: null,
|
||||
prev: null
|
||||
}
|
||||
|
||||
reload() {
|
||||
const reload = this.props.reloadOptions;
|
||||
reload && reload();
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleAdd(values: PlainObject) {
|
||||
this.saveRemote(values, 'add');
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleAddModalConfirm(values: Array<any>, action: Action, ctx: any, components: Array<any>) {
|
||||
this.saveRemote({
|
||||
...values,
|
||||
parent: this.state.parent
|
||||
}, 'add');
|
||||
this.closeAddDialog();
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleEdit(values: PlainObject) {
|
||||
this.saveRemote(values, 'edit');
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleEditModalConfirm(values: Array<any>, action: Action, ctx: any, components: Array<any>) {
|
||||
this.saveRemote({
|
||||
...values,
|
||||
prev: this.state.prev
|
||||
}, 'edit');
|
||||
this.closeEditDialog();
|
||||
}
|
||||
|
||||
@autobind
|
||||
async saveRemote(item: any, type: 'add' | 'edit') {
|
||||
const {
|
||||
addApi,
|
||||
editApi,
|
||||
data,
|
||||
env
|
||||
} = this.props;
|
||||
|
||||
let remote: Payload | null = null;
|
||||
if (type == 'add' && isEffectiveApi(addApi, createObject(data, item))) {
|
||||
remote = await env.fetcher(addApi, createObject(data, item));
|
||||
} else if (type == 'edit' && isEffectiveApi(editApi, createObject(data, item))) {
|
||||
remote = await env.fetcher(editApi, createObject(data, item));
|
||||
}
|
||||
|
||||
if (remote && !remote.ok) {
|
||||
env.notify('error', remote.msg || '保存失败');
|
||||
return;
|
||||
}
|
||||
|
||||
this.reload();
|
||||
}
|
||||
|
||||
@autobind
|
||||
async handleRemove(item: any) {
|
||||
const {deleteConfirmText, deleteApi, data, env} = this.props;
|
||||
const ctx = createObject(data, item);
|
||||
if (isEffectiveApi(deleteApi, ctx)) {
|
||||
const confirmed = await env.confirm(deleteConfirmText ? filter(deleteConfirmText, ctx) : '确认要删除?');
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await env.fetcher(deleteApi, ctx);
|
||||
|
||||
if (!result.ok) {
|
||||
env.notify('error', '删除失败');
|
||||
return;
|
||||
}
|
||||
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
openAddDialog(parent: Option | null) {
|
||||
this.setState({
|
||||
isAddModalOpened: true,
|
||||
parent
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
closeAddDialog() {
|
||||
this.setState({
|
||||
isAddModalOpened: false,
|
||||
parent: null
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
openEditDialog(prev: Option) {
|
||||
this.setState({
|
||||
isEditModalOpened: true,
|
||||
prev
|
||||
})
|
||||
}
|
||||
|
||||
@autobind
|
||||
closeEditDialog() {
|
||||
this.setState({
|
||||
isEditModalOpened: false,
|
||||
prev: null
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
@ -55,7 +187,14 @@ export default class TreeControl extends React.Component<TreeProps, any> {
|
||||
rootValue,
|
||||
showIcon,
|
||||
showRadio,
|
||||
render
|
||||
render,
|
||||
addMode,
|
||||
addApi,
|
||||
addDialog,
|
||||
editMode,
|
||||
editApi,
|
||||
editDialog,
|
||||
deleteApi
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -90,8 +229,46 @@ export default class TreeControl extends React.Component<TreeProps, any> {
|
||||
value={value || ''}
|
||||
nameField="label"
|
||||
selfDisabledAffectChildren={false}
|
||||
addMode={addMode}
|
||||
addable={isEffectiveApi(addApi)}
|
||||
onAdd={this.handleAdd}
|
||||
openAddDialog={this.openAddDialog}
|
||||
editMode={editMode}
|
||||
editable={isEffectiveApi(editApi)}
|
||||
onEdit={this.handleEdit}
|
||||
openEditDialog={this.openEditDialog}
|
||||
onRemove={this.handleRemove}
|
||||
deletable={isEffectiveApi(deleteApi)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{addMode && render(
|
||||
'modal',
|
||||
{
|
||||
type: 'dialog',
|
||||
...addDialog
|
||||
},
|
||||
{
|
||||
key: 'addModal',
|
||||
onConfirm: this.handleAddModalConfirm,
|
||||
onClose: this.closeAddDialog,
|
||||
show: this.state.isAddModalOpened
|
||||
}
|
||||
)}
|
||||
|
||||
{editMode && render(
|
||||
'modal',
|
||||
{
|
||||
type: 'dialog',
|
||||
...editDialog
|
||||
},
|
||||
{
|
||||
key: 'editModal',
|
||||
onConfirm: this.handleEditModalConfirm,
|
||||
onClose: this.closeEditDialog,
|
||||
show: this.state.isEditModalOpened
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user