forked from p96170835/amis
select search 框放到 popOver 中并支持新增\编辑\删除
This commit is contained in:
parent
e2d93eae04
commit
9d4b983d9e
|
@ -8,10 +8,7 @@
|
|||
background: $Form-select-bg;
|
||||
border-radius: $Form-select-borderRadius;
|
||||
height: $Form-selectOption-height;
|
||||
$paddingY: (
|
||||
$Form-selectOption-height - $Form-input-lineHeight *
|
||||
$Form-input-fontSize - $Form-select-borderWidth * 2
|
||||
)/2;
|
||||
$paddingY: ($Form-selectOption-height - $Form-input-lineHeight * $Form-input-fontSize - $Form-select-borderWidth * 2)/2;
|
||||
padding: $paddingY 0 $paddingY $Form-select-paddingX;
|
||||
cursor: pointer;
|
||||
color: $Form-select-color;
|
||||
|
@ -46,17 +43,7 @@
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
&-input {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
outline: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
line-height: $Form-input-lineHeight;
|
||||
height: $Form-input-lineHeight * $Form-input-fontSize;
|
||||
}
|
||||
|
||||
|
||||
&-value {
|
||||
line-height: $Form-input-lineHeight * $Form-input-fontSize;
|
||||
|
@ -64,6 +51,7 @@
|
|||
}
|
||||
|
||||
&--searchable {
|
||||
|
||||
.#{$ns}Select-placeholder,
|
||||
.#{$ns}Select-value {
|
||||
position: absolute;
|
||||
|
@ -79,22 +67,21 @@
|
|||
.#{$ns}Select-valueWrap {
|
||||
margin-bottom: -$gap-xs;
|
||||
|
||||
> input {
|
||||
>input {
|
||||
display: inline-block;
|
||||
width: px2rem(100px);
|
||||
margin-bottom: $gap-xs;
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns}Select-values + .#{$ns}Select-input {
|
||||
.#{$ns}Select-values+.#{$ns}Select-input {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.#{$ns}Select-value {
|
||||
position: static;
|
||||
user-select: none;
|
||||
line-height: $Form-input-lineHeight * $Form-input-fontSize -
|
||||
px2rem(2px);
|
||||
line-height: $Form-input-lineHeight * $Form-input-fontSize - px2rem(2px);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
font-size: $Form-selectValue-fontSize;
|
||||
|
@ -145,19 +132,18 @@
|
|||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
&-menuOuter {
|
||||
position: absolute;
|
||||
background: $Form-select-menu-bg;
|
||||
color: $Form-select-menu-color;
|
||||
border: $Form-select-outer-borderWidth solid
|
||||
$Form-input-onFocused-borderColor;
|
||||
left: px2rem(-1px);
|
||||
right: px2rem(-1px);
|
||||
min-width: 100%;
|
||||
top: $Form-select-outer-top;
|
||||
z-index: 10;
|
||||
box-shadow: $Form-select-outer-boxShadow;
|
||||
}
|
||||
// &-menuOuter {
|
||||
// position: absolute;
|
||||
// background: $Form-select-menu-bg;
|
||||
// color: $Form-select-menu-color;
|
||||
// border: $Form-select-outer-borderWidth solid $Form-input-onFocused-borderColor;
|
||||
// left: px2rem(-1px);
|
||||
// right: px2rem(-1px);
|
||||
// min-width: 100%;
|
||||
// top: $Form-select-outer-top;
|
||||
// z-index: 10;
|
||||
// box-shadow: $Form-select-outer-boxShadow;
|
||||
// }
|
||||
|
||||
&-menu {
|
||||
max-height: px2rem(300px);
|
||||
|
@ -165,24 +151,37 @@
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
&-checkAll {
|
||||
padding: (
|
||||
$Form-select-menu-height - $Form-input-lineHeight *
|
||||
$Form-input-fontSize - px2rem(2px)
|
||||
)/2 $Form-select-paddingX;
|
||||
border-bottom: px2rem(1px) solid $Form-select-checkall-bottomBorder;
|
||||
min-width: px2rem(100px);
|
||||
&-input {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
border: none;
|
||||
margin: 0 $Form-select-paddingX;
|
||||
padding: ($Form-input-height - $Form-input-lineHeight * $Form-input-fontSize)/2 0;
|
||||
line-height: $Form-input-lineHeight;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
>svg {
|
||||
fill: #999;
|
||||
width: px2rem(14px);
|
||||
height: px2rem(14px);
|
||||
margin-right: px2rem(5px);
|
||||
top: 0;
|
||||
}
|
||||
|
||||
>input {
|
||||
outline: none;
|
||||
border: none;
|
||||
flex-grow: 1;
|
||||
background: transparent;
|
||||
font-size: px2rem(12px);
|
||||
}
|
||||
}
|
||||
|
||||
&-option {
|
||||
padding: (
|
||||
$Form-select-menu-height - $Form-input-lineHeight *
|
||||
$Form-input-fontSize - px2rem(2px)
|
||||
)/2 $Form-select-paddingX;
|
||||
cursor: pointer;
|
||||
padding: ($Form-select-menu-height - $Form-input-lineHeight * $Form-input-fontSize)/2 $Form-select-paddingX;
|
||||
|
||||
&.is-active {
|
||||
color: $Form-select-menu-onActive-color;
|
||||
|
@ -202,12 +201,46 @@
|
|||
&--placeholder {
|
||||
color: $Form-input-placeholderColor;
|
||||
}
|
||||
|
||||
>label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
>a {
|
||||
float: right;
|
||||
margin-left: px2rem(5px);
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.is-highlight>a {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&-noResult {
|
||||
color: $Form-select-placeholderColor;
|
||||
line-height: $Form-input-lineHeight;
|
||||
user-select: none;
|
||||
margin: 5px 0 0;
|
||||
padding: ($Form-select-menu-height - $Form-input-lineHeight * $Form-input-fontSize)/2 $Form-select-paddingX;
|
||||
}
|
||||
|
||||
&-option-hl {
|
||||
color: $red;
|
||||
}
|
||||
|
||||
&-addBtn {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
padding: ($Form-select-menu-height - $Form-input-lineHeight * $Form-input-fontSize)/2 $Form-select-paddingX;
|
||||
|
||||
>svg {
|
||||
width: px2rem(14px);
|
||||
height: px2rem(14px);
|
||||
margin-right: $Checkbox-gap;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-focused,
|
||||
&.is-opened {
|
||||
border-color: $Form-input-onFocused-borderColor;
|
||||
|
@ -243,28 +276,33 @@
|
|||
}
|
||||
|
||||
.#{$ns}Select-popover {
|
||||
margin-top: -$Form-select-borderWidth;
|
||||
margin-top: px2rem(2px);
|
||||
background: $Form-select-menu-bg;
|
||||
color: $Form-select-menu-color;
|
||||
border: $Form-select-outer-borderWidth solid
|
||||
$Form-input-onFocused-borderColor;
|
||||
border: $Form-select-outer-borderWidth solid $Form-input-onFocused-borderColor;
|
||||
box-shadow: $Form-select-outer-boxShadow;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
min-width: px2rem(100px);
|
||||
z-index: 2;
|
||||
|
||||
&.#{$ns}PopOver--leftTopLeftBottom {
|
||||
margin-top: px2rem(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns}SelectControl:not(.is-inline) > .#{$ns}Select {
|
||||
.#{$ns}SelectControl:not(.is-inline)>.#{$ns}Select {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
// 需要能撑开
|
||||
@include media-breakpoint-up(sm) {
|
||||
.#{$ns}Form-control--sizeXs > .#{$ns}Select,
|
||||
.#{$ns}Form-control--sizeSm > .#{$ns}Select,
|
||||
.#{$ns}Form-control--sizeMd > .#{$ns}Select,
|
||||
.#{$ns}Form-control--sizeLg > .#{$ns}Select {
|
||||
|
||||
.#{$ns}Form-control--sizeXs>.#{$ns}Select,
|
||||
.#{$ns}Form-control--sizeSm>.#{$ns}Select,
|
||||
.#{$ns}Form-control--sizeMd>.#{$ns}Select,
|
||||
.#{$ns}Form-control--sizeLg>.#{$ns}Select {
|
||||
min-width: 100%;
|
||||
display: inline-flex !important;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -102,6 +102,7 @@ $Form-select-outer-boxShadow: px2rem(2px) px2rem(4px) px2rem(8px) rgba(0, 0, 0,
|
|||
$Form-select-menu-color: #333;
|
||||
$Form-select-menu-onHover-color: #000;
|
||||
$Form-select-menu-onHover-bg: #eaf6fe;
|
||||
$Form-select-menu-height: px2rem(24px);
|
||||
|
||||
$InputGroup-select-borderWidth: px2rem(1px);
|
||||
$InputGroup-select-bg: #f6f7fb;
|
||||
|
|
|
@ -11,8 +11,8 @@ import 'react-datetime/css/react-datetime.css';
|
|||
import Overlay from './Overlay';
|
||||
import PopOver from './PopOver';
|
||||
import Downshift, {ControllerStateAndHelpers} from 'downshift';
|
||||
import cx from 'classnames';
|
||||
import {closeIcon, Icon} from './icons';
|
||||
// @ts-ignore
|
||||
import matchSorter from 'match-sorter';
|
||||
import {noop} from '../utils/helper';
|
||||
import find = require('lodash/find');
|
||||
|
@ -46,6 +46,14 @@ export interface OptionProps {
|
|||
clearable?: boolean;
|
||||
placeholder?: string;
|
||||
autoFill?: {[propName: string]: any};
|
||||
creatable?: boolean;
|
||||
onAdd?: () => void;
|
||||
addControls?: Array<any>;
|
||||
editable?: boolean;
|
||||
editControls?: Array<any>;
|
||||
onEdit?: (value: Option) => void;
|
||||
removable?: boolean;
|
||||
onDelete?: (value: Option) => void;
|
||||
}
|
||||
|
||||
export type OptionValue = string | number | null | undefined | Option;
|
||||
|
@ -143,11 +151,14 @@ export function normalizeOptions(
|
|||
return [];
|
||||
}
|
||||
|
||||
interface SelectProps {
|
||||
const DownshiftChangeTypes = Downshift.stateChangeTypes;
|
||||
|
||||
interface SelectProps extends OptionProps {
|
||||
classPrefix: string;
|
||||
classnames: ClassNamesFn;
|
||||
className?: string;
|
||||
creatable: boolean;
|
||||
createBtnLabel: string;
|
||||
multiple: boolean;
|
||||
valueField: string;
|
||||
labelField: string;
|
||||
|
@ -167,9 +178,7 @@ interface SelectProps {
|
|||
inline: boolean;
|
||||
disabled: boolean;
|
||||
popOverContainer?: any;
|
||||
promptTextCreator: (label: string) => string;
|
||||
onChange: (value: void | string | Option | Array<Option>) => void;
|
||||
onNewOptionClick: (value: Option) => void;
|
||||
onFocus?: Function;
|
||||
onBlur?: Function;
|
||||
checkAll?: boolean;
|
||||
|
@ -191,17 +200,16 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
multiple: false,
|
||||
clearable: true,
|
||||
creatable: false,
|
||||
createBtnLabel: '新增选项',
|
||||
searchPromptText: '输入内容进行检索',
|
||||
loadingPlaceholder: '加载中..',
|
||||
noResultsText: '没有结果',
|
||||
noResultsText: '未找到任何结果',
|
||||
clearAllText: '移除所有',
|
||||
clearValueText: '移除',
|
||||
placeholder: '请选择',
|
||||
valueField: 'value',
|
||||
labelField: 'label',
|
||||
spinnerClassName: 'fa fa-spinner fa-spin fa-1x fa-fw',
|
||||
promptTextCreator: (label: string) => `新增:${label}`,
|
||||
onNewOptionClick: noop,
|
||||
inline: false,
|
||||
disabled: false,
|
||||
checkAll: false,
|
||||
|
@ -229,6 +237,9 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
this.handleKeyPress = this.handleKeyPress.bind(this);
|
||||
this.getTarget = this.getTarget.bind(this);
|
||||
this.toggleCheckAll = this.toggleCheckAll.bind(this);
|
||||
this.handleAddClick = this.handleAddClick.bind(this);
|
||||
this.handleEditClick = this.handleEditClick.bind(this);
|
||||
this.handleDeleteClick = this.handleDeleteClick.bind(this);
|
||||
|
||||
this.state = {
|
||||
isOpen: false,
|
||||
|
@ -244,22 +255,22 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
loadOptions,
|
||||
options,
|
||||
multiple,
|
||||
checkAll,
|
||||
defaultCheckAll,
|
||||
onChange,
|
||||
simpleValue
|
||||
} = this.props;
|
||||
let {selection} = this.state;
|
||||
|
||||
if (multiple && checkAll && defaultCheckAll && options.length) {
|
||||
if (multiple && defaultCheckAll && options.length) {
|
||||
selection = union(options, selection);
|
||||
this.setState(
|
||||
{
|
||||
selection: selection
|
||||
},
|
||||
() =>
|
||||
onChange(simpleValue ? selection.map(item => item.value) : selection)
|
||||
);
|
||||
this.setState({
|
||||
selection: selection
|
||||
});
|
||||
|
||||
// 因为等 State 设置完后再 onChange,会让 form 再 didMount 中的
|
||||
// onInit 出去的数据没有包含这部分,所以从 state 回调中拿出来了
|
||||
// 存在风险
|
||||
onChange(simpleValue ? selection.map(item => item.value) : selection);
|
||||
}
|
||||
|
||||
loadOptions && loadOptions('');
|
||||
|
@ -280,9 +291,13 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
|
||||
open() {
|
||||
this.props.disabled ||
|
||||
this.setState({
|
||||
isOpen: true
|
||||
});
|
||||
this.setState(
|
||||
{
|
||||
isOpen: true,
|
||||
highlightedIndex: -1
|
||||
},
|
||||
() => setTimeout(this.focus, 500)
|
||||
);
|
||||
}
|
||||
|
||||
close() {
|
||||
|
@ -301,9 +316,13 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
}
|
||||
|
||||
this.props.disabled ||
|
||||
this.setState({
|
||||
isOpen: !this.state.isOpen
|
||||
});
|
||||
this.setState(
|
||||
{
|
||||
isOpen: !this.state.isOpen,
|
||||
highlightedIndex: -1
|
||||
},
|
||||
this.state.isOpen ? undefined : () => setTimeout(this.focus, 500)
|
||||
);
|
||||
}
|
||||
|
||||
onFocus(e: any) {
|
||||
|
@ -389,14 +408,9 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
}
|
||||
|
||||
handleChange(selectItem: any) {
|
||||
const {onChange, multiple, onNewOptionClick, simpleValue} = this.props;
|
||||
const {onChange, multiple, simpleValue} = this.props;
|
||||
let {selection} = this.state;
|
||||
|
||||
if (selectItem.isNew) {
|
||||
delete selectItem.isNew;
|
||||
onNewOptionClick(selectItem);
|
||||
}
|
||||
|
||||
if (multiple) {
|
||||
selection = selection.concat();
|
||||
const idx = selection.indexOf(selectItem);
|
||||
|
@ -417,27 +431,26 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
const loadOptions = this.props.loadOptions;
|
||||
let doLoad = false;
|
||||
|
||||
if (changes.isOpen !== void 0) {
|
||||
update.isOpen = changes.isOpen;
|
||||
}
|
||||
|
||||
if (changes.highlightedIndex !== void 0) {
|
||||
update.highlightedIndex = changes.highlightedIndex;
|
||||
}
|
||||
|
||||
switch (changes.type) {
|
||||
case Downshift.stateChangeTypes.keyDownEnter:
|
||||
case Downshift.stateChangeTypes.clickItem:
|
||||
case DownshiftChangeTypes.keyDownEnter:
|
||||
case DownshiftChangeTypes.clickItem:
|
||||
update = {
|
||||
...update,
|
||||
inputValue: '',
|
||||
isOpen: multiple && checkAll ? true : false,
|
||||
isOpen: multiple ? true : false,
|
||||
isFocused: multiple && checkAll ? true : false
|
||||
};
|
||||
doLoad = true;
|
||||
break;
|
||||
case Downshift.stateChangeTypes.changeInput:
|
||||
case DownshiftChangeTypes.changeInput:
|
||||
update.highlightedIndex = 0;
|
||||
case DownshiftChangeTypes.keyDownArrowDown:
|
||||
case DownshiftChangeTypes.keyDownArrowUp:
|
||||
case DownshiftChangeTypes.itemMouseEnter:
|
||||
update = {
|
||||
...update,
|
||||
...changes
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -462,30 +475,38 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
onChange('');
|
||||
}
|
||||
|
||||
handleAddClick() {
|
||||
const {onAdd} = this.props;
|
||||
onAdd && onAdd();
|
||||
}
|
||||
|
||||
handleEditClick(e: Event, item: any) {
|
||||
const {onEdit} = this.props;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onEdit && onEdit(item);
|
||||
}
|
||||
|
||||
handleDeleteClick(e: Event, item: any) {
|
||||
const {onDelete} = this.props;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onDelete && onDelete(item);
|
||||
}
|
||||
|
||||
renderValue({inputValue, isOpen}: ControllerStateAndHelpers<any>) {
|
||||
const {
|
||||
multiple,
|
||||
placeholder,
|
||||
classPrefix: ns,
|
||||
labelField,
|
||||
searchable,
|
||||
creatable,
|
||||
disabled
|
||||
} = this.props;
|
||||
|
||||
const selection = this.state.selection;
|
||||
|
||||
if (
|
||||
searchable &&
|
||||
!creatable &&
|
||||
inputValue &&
|
||||
(multiple ? !selection.length : true)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!selection.length) {
|
||||
return creatable && inputValue ? null : (
|
||||
return (
|
||||
<div key="placeholder" className={`${ns}Select-placeholder`}>
|
||||
{placeholder}
|
||||
</div>
|
||||
|
@ -505,7 +526,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
{item[labelField || 'label']}
|
||||
</span>
|
||||
</div>
|
||||
) : inputValue && isOpen ? null : (
|
||||
) : (
|
||||
<div className={`${ns}Select-value`} key={index}>
|
||||
{item.label}
|
||||
</div>
|
||||
|
@ -513,13 +534,16 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
);
|
||||
}
|
||||
|
||||
renderOuter({
|
||||
selectedItem,
|
||||
getItemProps,
|
||||
highlightedIndex,
|
||||
inputValue,
|
||||
isOpen
|
||||
}: ControllerStateAndHelpers<any>) {
|
||||
renderOuter(
|
||||
{
|
||||
selectedItem,
|
||||
getItemProps,
|
||||
highlightedIndex,
|
||||
inputValue,
|
||||
isOpen
|
||||
}: ControllerStateAndHelpers<any>,
|
||||
getInputProps: any
|
||||
) {
|
||||
const {
|
||||
popOverContainer,
|
||||
options,
|
||||
|
@ -528,11 +552,16 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
noResultsText,
|
||||
loadOptions,
|
||||
creatable,
|
||||
promptTextCreator,
|
||||
multiple,
|
||||
classnames: cx,
|
||||
checkAll,
|
||||
checkAllLabel
|
||||
checkAllLabel,
|
||||
searchable,
|
||||
createBtnLabel,
|
||||
disabled,
|
||||
searchPromptText,
|
||||
editable,
|
||||
removable
|
||||
} = this.props;
|
||||
const {selection} = this.state;
|
||||
|
||||
|
@ -545,39 +574,37 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
})
|
||||
: options.concat();
|
||||
|
||||
if (multiple) {
|
||||
if (checkAll) {
|
||||
const optionsValues = options.map(option => option.value);
|
||||
const selectionValues = selection.map(select => select.value);
|
||||
checkedAll = optionsValues.every(
|
||||
option => selectionValues.indexOf(option) > -1
|
||||
);
|
||||
checkedPartial = optionsValues.some(
|
||||
option => selectionValues.indexOf(option) > -1
|
||||
);
|
||||
} else {
|
||||
filtedOptions = filtedOptions.filter(
|
||||
(option: any) => !~selectedItem.indexOf(option)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
inputValue &&
|
||||
creatable &&
|
||||
!find(options, item => item[labelField || 'label'] == inputValue)
|
||||
) {
|
||||
filtedOptions.unshift({
|
||||
[labelField]: inputValue,
|
||||
[valueField]: inputValue,
|
||||
isNew: true
|
||||
});
|
||||
if (multiple && checkAll) {
|
||||
const optionsValues = options.map(option => option.value);
|
||||
const selectionValues = selection.map(select => select.value);
|
||||
checkedAll = optionsValues.every(
|
||||
option => selectionValues.indexOf(option) > -1
|
||||
);
|
||||
checkedPartial = optionsValues.some(
|
||||
option => selectionValues.indexOf(option) > -1
|
||||
);
|
||||
}
|
||||
|
||||
const menu = (
|
||||
<div ref={this.menu} className={cx('Select-menu')}>
|
||||
{multiple && checkAll ? (
|
||||
<div className={cx('Select-checkAll')}>
|
||||
{searchable ? (
|
||||
<div className={cx(`Select-input`)}>
|
||||
<Icon icon="search" className="icon" />
|
||||
<input
|
||||
{...getInputProps({
|
||||
onFocus: this.onFocus,
|
||||
onBlur: this.onBlur,
|
||||
disabled: disabled,
|
||||
placeholder: searchPromptText,
|
||||
onChange: this.handleInputChange,
|
||||
ref: this.inputRef
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{multiple && checkAll && filtedOptions.length ? (
|
||||
<div className={cx('Select-option')}>
|
||||
<Checkbox
|
||||
checked={checkedPartial}
|
||||
partial={checkedPartial && !checkedAll}
|
||||
|
@ -587,11 +614,12 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
</Checkbox>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{filtedOptions.length ? (
|
||||
filtedOptions.map((item, index) => {
|
||||
const checked = checkAll
|
||||
? selection.some((o: Option) => o.value == item.value)
|
||||
: false;
|
||||
: !!~selectedItem.indexOf(item);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -609,15 +637,32 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
(Array.isArray(selectedItem) && ~selectedItem.indexOf(item))
|
||||
})}
|
||||
>
|
||||
{checkAll ? (
|
||||
{removable ? (
|
||||
<a data-tooltip="移除" data-position="left">
|
||||
<Icon
|
||||
icon="minus"
|
||||
className="icon"
|
||||
onClick={(e: any) => this.handleDeleteClick(e, item)}
|
||||
/>
|
||||
</a>
|
||||
) : null}
|
||||
{editable ? (
|
||||
<a data-tooltip="编辑" data-position="left">
|
||||
<Icon
|
||||
icon="pencil"
|
||||
className="icon"
|
||||
onClick={(e: any) => this.handleEditClick(e, item)}
|
||||
/>
|
||||
</a>
|
||||
) : null}
|
||||
|
||||
{checkAll || multiple ? (
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
trueValue={item.value}
|
||||
onChange={() => this.handleChange(item)}
|
||||
>
|
||||
{item.isNew
|
||||
? promptTextCreator(item.label as string)
|
||||
: item.disabled
|
||||
{item.disabled
|
||||
? item[labelField]
|
||||
: highlight(
|
||||
item[labelField],
|
||||
|
@ -625,8 +670,6 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
cx('Select-option-hl')
|
||||
)}
|
||||
</Checkbox>
|
||||
) : item.isNew ? (
|
||||
promptTextCreator(item.label as string)
|
||||
) : (
|
||||
<span>
|
||||
{item.disabled
|
||||
|
@ -643,32 +686,56 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
);
|
||||
})
|
||||
) : (
|
||||
<div className={cx('Select-option Select-option--placeholder')}>
|
||||
{noResultsText}
|
||||
</div>
|
||||
<div className={cx('Select-noResult')}>{noResultsText}</div>
|
||||
)}
|
||||
|
||||
{creatable && !disabled ? (
|
||||
<a className={cx('Select-addBtn')} onClick={this.handleAddClick}>
|
||||
<Icon icon="plus" className="icon" />
|
||||
{createBtnLabel}
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (popOverContainer) {
|
||||
return (
|
||||
<Overlay
|
||||
container={popOverContainer}
|
||||
placement="left-bottom-left-top"
|
||||
target={this.getTarget}
|
||||
show
|
||||
return (
|
||||
<Overlay
|
||||
container={popOverContainer || this.getTarget}
|
||||
target={this.getTarget}
|
||||
show
|
||||
>
|
||||
<PopOver
|
||||
overlay
|
||||
className={cx('Select-popover')}
|
||||
style={{width: this.target ? this.target.offsetWidth : 'auto'}}
|
||||
onHide={this.close}
|
||||
>
|
||||
<PopOver
|
||||
className={cx('Select-popover')}
|
||||
style={{width: this.target ? this.target.offsetWidth : 'auto'}}
|
||||
>
|
||||
{menu}
|
||||
</PopOver>
|
||||
</Overlay>
|
||||
);
|
||||
} else {
|
||||
return <div className={cx('Select-menuOuter')}>{menu}</div>;
|
||||
}
|
||||
{menu}
|
||||
</PopOver>
|
||||
</Overlay>
|
||||
);
|
||||
|
||||
// if (popOverContainer) {
|
||||
// return (
|
||||
// <Overlay
|
||||
// container={popOverContainer}
|
||||
// placement="left-bottom-left-top"
|
||||
// target={this.getTarget}
|
||||
// show
|
||||
// >
|
||||
// <PopOver
|
||||
// overlay
|
||||
// className={cx('Select-popover')}
|
||||
// style={{width: this.target ? this.target.offsetWidth : 'auto'}}
|
||||
// onHide={this.close}
|
||||
// >
|
||||
// {menu}
|
||||
// </PopOver>
|
||||
// </Overlay>
|
||||
// );
|
||||
// } else {
|
||||
// return <div className={cx('Select-menuOuter')}>{menu}</div>;
|
||||
// }
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -697,14 +764,14 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
inputValue={inputValue}
|
||||
onChange={this.handleChange}
|
||||
onStateChange={this.handleStateChange}
|
||||
onOuterClick={this.close}
|
||||
// onOuterClick={this.close}
|
||||
itemToString={item => (item ? item[labelField] : '')}
|
||||
>
|
||||
{(options: ControllerStateAndHelpers<any>) => {
|
||||
const {isOpen, getInputProps} = options;
|
||||
return (
|
||||
<div
|
||||
tabIndex={searchable || disabled ? -1 : 0}
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
onClick={this.toggle}
|
||||
onFocus={this.onFocus}
|
||||
|
@ -724,22 +791,6 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
>
|
||||
<div className={cx(`Select-valueWrap`)}>
|
||||
{this.renderValue(options)}
|
||||
{searchable && !disabled ? (
|
||||
<input
|
||||
{...getInputProps({
|
||||
className: cx(`Select-input`),
|
||||
onFocus: this.onFocus,
|
||||
onBlur: this.onBlur,
|
||||
onKeyDown: event => {
|
||||
if (event.key === 'Backspace' && !inputValue) {
|
||||
this.removeItem(value.length - 1);
|
||||
}
|
||||
},
|
||||
onChange: this.handleInputChange,
|
||||
ref: this.inputRef
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{clearable && !disabled && value && value.length ? (
|
||||
<a onClick={this.clearValue} className={cx('Select-clear')}>
|
||||
|
@ -753,7 +804,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
|||
) : null}
|
||||
|
||||
<span className={cx('Select-arrow')} />
|
||||
{isOpen ? this.renderOuter(options) : null}
|
||||
{isOpen ? this.renderOuter(options, getInputProps) : null}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
|
|
|
@ -48,6 +48,9 @@ import SuccessIcon from '../icons/success.svg';
|
|||
// @ts-ignore
|
||||
import FailIcon from '../icons/fail.svg';
|
||||
|
||||
// @ts-ignore
|
||||
import SearchIcon from '../icons/search.svg';
|
||||
|
||||
// 兼容原来的用法,后续不直接试用。
|
||||
// @ts-ignore
|
||||
export const closeIcon = <CloseIcon />;
|
||||
|
@ -103,6 +106,7 @@ registerIcon('upload', UploadIcon);
|
|||
registerIcon('file', FileIcon);
|
||||
registerIcon('success', SuccessIcon);
|
||||
registerIcon('fail', FailIcon);
|
||||
registerIcon('search', SearchIcon);
|
||||
|
||||
export function Icon({
|
||||
icon,
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 18 18" version="1.1">
|
||||
<path d="M2,8 C2,4.691 4.691,2 8,2 C11.309,2 14,4.691 14,8 C14,11.309 11.309,14 8,14 C4.691,14 2,11.309 2,8 L2,8 Z M18,16.586 L14.314,12.9 C15.367,11.545 16,9.849 16,8 C16,3.582 12.418,0 8,0 C3.582,0 0,3.582 0,8 C0,12.418 3.582,16 8,16 C9.849,16 11.545,15.367 12.9,14.314 L16.586,18 L18,16.586 Z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 438 B |
|
@ -135,7 +135,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
|||
control: any;
|
||||
lastQuery: any;
|
||||
dataInvalid: boolean = false;
|
||||
timer: number;
|
||||
timer: NodeJS.Timeout;
|
||||
mounted: boolean;
|
||||
constructor(props: CRUDProps) {
|
||||
super(props);
|
||||
|
@ -331,7 +331,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
|||
env.jumpTo(filter(action.redirect, data), action);
|
||||
|
||||
return store
|
||||
.saveRemote(action.api, data, {
|
||||
.saveRemote(action.api!, data, {
|
||||
successMessage:
|
||||
(action.messages && action.messages.success) ||
|
||||
(messages && messages.saveSuccess),
|
||||
|
|
|
@ -118,16 +118,7 @@ export default class DropDownButton extends React.Component<
|
|||
|
||||
if (popOverContainer) {
|
||||
return (
|
||||
<Overlay
|
||||
container={popOverContainer}
|
||||
placement={
|
||||
align === 'right'
|
||||
? 'right-bottom-right-top'
|
||||
: 'left-bottom-left-top'
|
||||
}
|
||||
target={() => this.target}
|
||||
show
|
||||
>
|
||||
<Overlay container={popOverContainer} target={() => this.target} show>
|
||||
<PopOver
|
||||
overlay
|
||||
onHide={this.close}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {IFormStore, IFormItemStore} from '../../store/form';
|
||||
import debouce = require('lodash/debounce');
|
||||
|
||||
|
|
|
@ -10,9 +10,10 @@ import {
|
|||
RendererConfig,
|
||||
HocStoreFactory
|
||||
} from '../../factory';
|
||||
import {anyChanged, ucFirst, getWidthRate} from '../../utils/helper';
|
||||
import {anyChanged, ucFirst, getWidthRate, autobind} from '../../utils/helper';
|
||||
import {observer} from 'mobx-react';
|
||||
import {FormHorizontal, FormSchema} from '.';
|
||||
import {Schema} from '../../types';
|
||||
|
||||
export interface FormItemBasicConfig extends Partial<RendererConfig> {
|
||||
type?: string;
|
||||
|
@ -33,13 +34,11 @@ export interface FormItemBasicConfig extends Partial<RendererConfig> {
|
|||
validate?: (values: any, value: any) => string | boolean;
|
||||
}
|
||||
|
||||
export interface FormItemState {
|
||||
isFocused: boolean;
|
||||
}
|
||||
|
||||
// 自己接收到属性。
|
||||
export interface FormItemProps extends RendererProps {
|
||||
name?: string;
|
||||
formStore?: IFormStore;
|
||||
formItem?: IFormItemStore;
|
||||
formInited: boolean;
|
||||
formMode: 'normal' | 'horizontal' | 'inline' | 'row' | 'default';
|
||||
formHorizontal: FormHorizontal;
|
||||
|
@ -89,8 +88,10 @@ export interface FormItemProps extends RendererProps {
|
|||
error?: string;
|
||||
}
|
||||
|
||||
export type FormControlProps = RendererProps &
|
||||
Exclude<
|
||||
// 下发下去的属性
|
||||
export type FormControlProps = RendererProps & {
|
||||
onOpenDialog: (schema: Schema, data: any) => Promise<any>;
|
||||
} & Exclude<
|
||||
FormItemProps,
|
||||
| 'inputClassName'
|
||||
| 'renderControl'
|
||||
|
@ -114,29 +115,15 @@ export interface FormItemConfig extends FormItemBasicConfig {
|
|||
component: FormControlComponent;
|
||||
}
|
||||
|
||||
export class FormItemWrap extends React.Component<
|
||||
FormItemProps,
|
||||
FormItemState
|
||||
> {
|
||||
export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
reaction: any;
|
||||
|
||||
constructor(props: FormItemProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isFocused: false
|
||||
};
|
||||
|
||||
this.handleFocus = this.handleFocus.bind(this);
|
||||
this.handleBlur = this.handleBlur.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const {formItem: model} = this.props;
|
||||
|
||||
if (model) {
|
||||
this.reaction = reaction(
|
||||
() => model.errors.join(''),
|
||||
() => `${model.errors.join('')}${model.isFocused}${model.dialogOpen}`,
|
||||
() => this.forceUpdate()
|
||||
);
|
||||
}
|
||||
|
@ -146,20 +133,51 @@ export class FormItemWrap extends React.Component<
|
|||
this.reaction && this.reaction();
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleFocus(e: any) {
|
||||
this.setState({
|
||||
isFocused: true
|
||||
});
|
||||
const {formItem: model} = this.props;
|
||||
model && model.focus();
|
||||
this.props.onFocus && this.props.onFocus(e);
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleBlur(e: any) {
|
||||
this.setState({
|
||||
isFocused: false
|
||||
});
|
||||
const {formItem: model} = this.props;
|
||||
model && model.blur();
|
||||
this.props.onBlur && this.props.onBlur(e);
|
||||
}
|
||||
|
||||
@autobind
|
||||
async handleOpenDialog(schema: Schema, data: any) {
|
||||
const {formItem: model} = this.props;
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise(resolve =>
|
||||
model.openDialog(schema, data, (result?: any) => resolve(result))
|
||||
);
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleDialogConfirm([values]: Array<any>) {
|
||||
const {formItem: model} = this.props;
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
model.closeDialog(values);
|
||||
}
|
||||
|
||||
@autobind
|
||||
handleDialogClose() {
|
||||
const {formItem: model} = this.props;
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
model.closeDialog();
|
||||
}
|
||||
|
||||
renderControl() {
|
||||
const {
|
||||
inputClassName,
|
||||
|
@ -179,6 +197,7 @@ export class FormItemWrap extends React.Component<
|
|||
const controlSize = size || defaultSize;
|
||||
return renderControl({
|
||||
...rest,
|
||||
onOpenDialog: this.handleOpenDialog,
|
||||
type,
|
||||
classnames: cx,
|
||||
formItem: model,
|
||||
|
@ -299,7 +318,7 @@ export class FormItemWrap extends React.Component<
|
|||
})
|
||||
: null}
|
||||
|
||||
{hint && this.state.isFocused
|
||||
{hint && model && model.isFocused
|
||||
? render('hint', hint, {
|
||||
className: cx(`Form-hint`)
|
||||
})
|
||||
|
@ -395,7 +414,7 @@ export class FormItemWrap extends React.Component<
|
|||
})
|
||||
: null}
|
||||
|
||||
{hint && this.state.isFocused
|
||||
{hint && model && model.isFocused
|
||||
? render('hint', hint, {
|
||||
className: cx(`Form-hint`)
|
||||
})
|
||||
|
@ -490,7 +509,7 @@ export class FormItemWrap extends React.Component<
|
|||
})
|
||||
: null}
|
||||
|
||||
{hint && this.state.isFocused
|
||||
{hint && model && model.isFocused
|
||||
? render('hint', hint, {
|
||||
className: cx(`Form-hint`)
|
||||
})
|
||||
|
@ -588,7 +607,7 @@ export class FormItemWrap extends React.Component<
|
|||
: null}
|
||||
</div>
|
||||
|
||||
{hint && this.state.isFocused
|
||||
{hint && model && model.isFocused
|
||||
? render('hint', hint, {
|
||||
className: cx(`Form-hint`)
|
||||
})
|
||||
|
@ -612,19 +631,38 @@ export class FormItemWrap extends React.Component<
|
|||
}
|
||||
|
||||
render() {
|
||||
const {formMode, inputOnly, wrap} = this.props;
|
||||
const {formMode, inputOnly, wrap, render, formItem: model} = this.props;
|
||||
|
||||
if (wrap === false || inputOnly) {
|
||||
return this.renderControl();
|
||||
}
|
||||
|
||||
return formMode === 'inline'
|
||||
? this.renderInline()
|
||||
: formMode === 'horizontal'
|
||||
? this.renderHorizontal()
|
||||
: formMode === 'row'
|
||||
? this.renderRow()
|
||||
: this.renderNormal();
|
||||
return (
|
||||
<>
|
||||
{formMode === 'inline'
|
||||
? this.renderInline()
|
||||
: formMode === 'horizontal'
|
||||
? this.renderHorizontal()
|
||||
: formMode === 'row'
|
||||
? this.renderRow()
|
||||
: this.renderNormal()}
|
||||
{model
|
||||
? render(
|
||||
'modal',
|
||||
{
|
||||
type: 'dialog',
|
||||
...model.dialogSchema
|
||||
},
|
||||
{
|
||||
show: model.dialogOpen,
|
||||
onClose: this.handleDialogClose,
|
||||
onConfirm: this.handleDialogConfirm,
|
||||
data: model.dialogData
|
||||
}
|
||||
)
|
||||
: null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -796,6 +834,7 @@ export function registerFormItem(config: FormItemConfig): RendererConfig {
|
|||
return (
|
||||
<Control
|
||||
{...rest}
|
||||
onOpenDialog={this.handleOpenDialog}
|
||||
size={config.sizeMutable !== false ? undefined : size}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
|
|
|
@ -308,12 +308,7 @@ export default class NestedSelectControl extends React.Component<
|
|||
|
||||
if (popOverContainer) {
|
||||
return (
|
||||
<Overlay
|
||||
container={popOverContainer}
|
||||
placement="left-bottom-left-top right-bottom-right-top"
|
||||
target={() => this.target}
|
||||
show
|
||||
>
|
||||
<Overlay container={popOverContainer} target={() => this.target} show>
|
||||
<PopOver
|
||||
className={cx('NestedSelect-popover')}
|
||||
style={{minWidth: this.target.offsetWidth}}
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
import {Api, Schema} from '../../types';
|
||||
import {
|
||||
buildApi,
|
||||
isEffectiveApi,
|
||||
isValidApi,
|
||||
isApiOutdated
|
||||
} from '../../utils/api';
|
||||
import {anyChanged, autobind} from '../../utils/helper';
|
||||
import {isEffectiveApi, isApiOutdated} from '../../utils/api';
|
||||
import {anyChanged, autobind, createObject} from '../../utils/helper';
|
||||
import {reaction} from 'mobx';
|
||||
import {FormControlProps, registerFormItem, FormItemBasicConfig} from './Item';
|
||||
import {IFormItemStore} from '../../store/formItem';
|
||||
|
@ -13,8 +8,9 @@ export type OptionsControlComponent = React.ComponentType<FormControlProps>;
|
|||
|
||||
import React from 'react';
|
||||
import {resolveVariableAndFilter} from '../../utils/tpl-builtin';
|
||||
import {evalExpression} from '../../utils/tpl';
|
||||
import {Option, OptionProps, normalizeOptions} from '../../components/Select';
|
||||
import {filter} from '../../utils/tpl';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
|
||||
export {Option};
|
||||
|
||||
|
@ -26,6 +22,7 @@ export interface OptionsConfig extends OptionsBasicConfig {
|
|||
component: React.ComponentType<OptionsControlProps>;
|
||||
}
|
||||
|
||||
// 下发给注册进来的组件的属性。
|
||||
export interface OptionsControlProps extends FormControlProps, OptionProps {
|
||||
source?: Api;
|
||||
name?: string;
|
||||
|
@ -35,22 +32,24 @@ export interface OptionsControlProps extends FormControlProps, OptionProps {
|
|||
setOptions: (value: Array<any>) => void;
|
||||
setLoading: (value: boolean) => void;
|
||||
reloadOptions: () => void;
|
||||
addable?: boolean;
|
||||
creatable?: boolean;
|
||||
onAdd?: () => void;
|
||||
addControls?: Array<any>;
|
||||
editable?: boolean;
|
||||
editControls?: Array<any>;
|
||||
onEdit?: (value: Option) => void;
|
||||
removable?: boolean;
|
||||
onDelete?: (value: Option) => void;
|
||||
}
|
||||
|
||||
// 自己接收的属性。
|
||||
export interface OptionsProps extends FormControlProps, OptionProps {
|
||||
sourcce?: Api;
|
||||
source?: Api;
|
||||
creatable?: boolean;
|
||||
addApi?: Api;
|
||||
addMode?: 'dialog' | 'normal';
|
||||
addDialog?: Schema;
|
||||
addControls?: Array<any>;
|
||||
editApi?: Api;
|
||||
editMode?: 'dialog' | 'normal';
|
||||
editDialog?: Schema;
|
||||
editControls?: Array<any>;
|
||||
deleteApi?: Api;
|
||||
deleteConfirmText?: string;
|
||||
}
|
||||
|
@ -58,7 +57,6 @@ export interface OptionsProps extends FormControlProps, OptionProps {
|
|||
export function registerOptionsControl(config: OptionsConfig) {
|
||||
const Control = config.component;
|
||||
|
||||
// @observer
|
||||
class FormOptionsItem extends React.Component<OptionsProps, any> {
|
||||
static displayName = `OptionsControl(${config.type})`;
|
||||
static defaultProps = {
|
||||
|
@ -70,6 +68,7 @@ export function registerOptionsControl(config: OptionsConfig) {
|
|||
multiple: false,
|
||||
placeholder: '请选择',
|
||||
resetValue: '',
|
||||
deleteConfirmText: '确定要删除?',
|
||||
...Control.defaultProps
|
||||
};
|
||||
static propsList: any = (Control as any).propsList
|
||||
|
@ -113,10 +112,10 @@ export function registerOptionsControl(config: OptionsConfig) {
|
|||
|
||||
let loadOptions: boolean = initFetch !== false;
|
||||
|
||||
if (/^\$(?:([a-z0-9_.]+)|{.+})$/.test(source) && formItem) {
|
||||
if (/^\$(?:([a-z0-9_.]+)|{.+})$/.test(source as string) && formItem) {
|
||||
formItem.setOptions(
|
||||
normalizeOptions(
|
||||
resolveVariableAndFilter(source, data, '| raw') || []
|
||||
resolveVariableAndFilter(source as string, data, '| raw') || []
|
||||
)
|
||||
);
|
||||
loadOptions = false;
|
||||
|
@ -207,7 +206,7 @@ export function registerOptionsControl(config: OptionsConfig) {
|
|||
) {
|
||||
if (/^\$(?:([a-z0-9_.]+)|{.+})$/.test(props.source as string)) {
|
||||
const prevOptions = resolveVariableAndFilter(
|
||||
prevProps.source,
|
||||
prevProps.source as string,
|
||||
prevProps.data,
|
||||
'| raw'
|
||||
);
|
||||
|
@ -439,8 +438,189 @@ export function registerOptionsControl(config: OptionsConfig) {
|
|||
formItem && formItem.setLoading(value);
|
||||
}
|
||||
|
||||
@autobind
|
||||
async handleOptionAdd() {
|
||||
let {
|
||||
addControls,
|
||||
disabled,
|
||||
labelField,
|
||||
onOpenDialog,
|
||||
addApi,
|
||||
source,
|
||||
data,
|
||||
valueField,
|
||||
formItem: model,
|
||||
createBtnLabel
|
||||
} = this.props;
|
||||
|
||||
if (disabled || !model) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(addControls) || !addControls.length) {
|
||||
addControls = [
|
||||
{
|
||||
type: 'text',
|
||||
name: labelField || 'label',
|
||||
label: false,
|
||||
placeholder: '请输入名称'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
let result: any = await onOpenDialog(
|
||||
{
|
||||
type: 'dialog',
|
||||
title: createBtnLabel || '新增选项',
|
||||
body: {
|
||||
type: 'form',
|
||||
api: addApi,
|
||||
controls: addControls
|
||||
}
|
||||
},
|
||||
data
|
||||
);
|
||||
|
||||
if (result) {
|
||||
// 没走服务端的。
|
||||
if (!result.__saved) {
|
||||
result = {
|
||||
...result,
|
||||
[valueField || 'value']: result[labelField || 'label']
|
||||
};
|
||||
}
|
||||
|
||||
source
|
||||
? this.reload()
|
||||
: model.setOptions(model.options.concat({...result}));
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
async handleOptionEdit(value: any) {
|
||||
let {
|
||||
editControls,
|
||||
disabled,
|
||||
labelField,
|
||||
onOpenDialog,
|
||||
editApi,
|
||||
source,
|
||||
data,
|
||||
formItem: model,
|
||||
valueField
|
||||
} = this.props;
|
||||
|
||||
if (disabled || !model) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(editControls) || !editControls.length) {
|
||||
editControls = [
|
||||
{
|
||||
type: 'text',
|
||||
name: labelField || 'label',
|
||||
label: false,
|
||||
placeholder: '请输入名称'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
let result = await onOpenDialog(
|
||||
{
|
||||
type: 'dialog',
|
||||
title: '编辑选项',
|
||||
body: {
|
||||
type: 'form',
|
||||
api: editApi,
|
||||
controls: editControls
|
||||
}
|
||||
},
|
||||
createObject(data, value)
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source) {
|
||||
this.reload();
|
||||
} else {
|
||||
const options = model.options.concat();
|
||||
const idx = findIndex(
|
||||
options,
|
||||
item => item[valueField || 'value'] == result[valueField || 'value']
|
||||
);
|
||||
|
||||
if (~idx) {
|
||||
options.splice(idx, 1, {
|
||||
...options[idx],
|
||||
...result
|
||||
});
|
||||
model.setOptions(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
async handleOptionDelete(value: any) {
|
||||
let {
|
||||
deleteConfirmText,
|
||||
disabled,
|
||||
data,
|
||||
deleteApi,
|
||||
env,
|
||||
formItem: model,
|
||||
source,
|
||||
valueField
|
||||
} = this.props;
|
||||
|
||||
if (disabled || !model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = createObject(data, value);
|
||||
const confirmed = deleteConfirmText
|
||||
? await env.confirm(filter(deleteConfirmText, ctx))
|
||||
: true;
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await env.fetcher(deleteApi!, ctx);
|
||||
|
||||
if (!result.ok) {
|
||||
env.notify('error', result.msg || '删除失败,请重试');
|
||||
} else if (source) {
|
||||
this.reload();
|
||||
} else {
|
||||
const options = model.options.concat();
|
||||
const idx = findIndex(
|
||||
options,
|
||||
item => item[valueField || 'value'] == value[valueField || 'value']
|
||||
);
|
||||
|
||||
if (~idx) {
|
||||
options.splice(idx, 1);
|
||||
model.setOptions(options);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
env.notify('error', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {value, formItem} = this.props;
|
||||
const {
|
||||
value,
|
||||
formItem,
|
||||
addApi,
|
||||
editApi,
|
||||
deleteApi,
|
||||
creatable,
|
||||
editable
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Control
|
||||
|
@ -455,6 +635,12 @@ export function registerOptionsControl(config: OptionsConfig) {
|
|||
setOptions={this.setOptions}
|
||||
syncOptions={this.syncOptions}
|
||||
reloadOptions={this.reload}
|
||||
creatable={creatable || isEffectiveApi(addApi)}
|
||||
editable={editable || isEffectiveApi(editApi)}
|
||||
removable={isEffectiveApi(deleteApi)}
|
||||
onAdd={this.handleOptionAdd}
|
||||
onEdit={this.handleOptionEdit}
|
||||
onDelete={this.handleOptionDelete}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
|||
leading: false
|
||||
});
|
||||
this.inputRef = this.inputRef.bind(this);
|
||||
this.handleNewOptionClick = this.handleNewOptionClick.bind(this);
|
||||
}
|
||||
|
||||
inputRef(ref: any) {
|
||||
|
@ -161,16 +160,6 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
|||
return combinedOptions;
|
||||
}
|
||||
|
||||
handleNewOptionClick(option: any) {
|
||||
const {setOptions, options} = this.props;
|
||||
|
||||
let mergedOptions: Array<any> = options.concat();
|
||||
mergedOptions.push({
|
||||
...option
|
||||
});
|
||||
setOptions(mergedOptions);
|
||||
}
|
||||
|
||||
reload() {
|
||||
const reload = this.props.reloadOptions;
|
||||
reload && reload();
|
||||
|
@ -211,7 +200,6 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
|||
ref={this.inputRef}
|
||||
value={selectedOptions}
|
||||
options={options}
|
||||
onNewOptionClick={this.handleNewOptionClick}
|
||||
loadOptions={
|
||||
isEffectiveApi(autoComplete) ? this.loadRemote : undefined
|
||||
}
|
||||
|
|
|
@ -395,7 +395,6 @@ export default class TreeSelectControl extends React.Component<
|
|||
return (
|
||||
<Overlay
|
||||
container={popOverContainer || (() => this.container.current)}
|
||||
placement="left-bottom-left-top right-bottom-right-top"
|
||||
target={() => this.target.current}
|
||||
show
|
||||
>
|
||||
|
|
|
@ -421,7 +421,6 @@ export const HocQuickEdit = (config: Partial<QuickEditConfig> = {}) => (
|
|||
return (
|
||||
<Overlay
|
||||
container={popOverContainer}
|
||||
placement="left-top right-top left-bottom right-bottom"
|
||||
target={() => this.target}
|
||||
onHide={this.closeQuickEdit}
|
||||
show
|
||||
|
|
|
@ -5,17 +5,12 @@ import {Api, Payload, fetchOptions} from '../types';
|
|||
import {ComboStore, IComboStore, IUniqueGroup} from './combo';
|
||||
import {evalExpression} from '../utils/tpl';
|
||||
import findIndex = require('lodash/findIndex');
|
||||
import {
|
||||
isArrayChilrenModified,
|
||||
hasOwnProperty,
|
||||
isObject,
|
||||
createObject
|
||||
} from '../utils/helper';
|
||||
import {isArrayChilrenModified, isObject, createObject} from '../utils/helper';
|
||||
import {flattenTree} from '../utils/helper';
|
||||
import {IRendererStore} from '.';
|
||||
import {normalizeOptions} from '../components/Select';
|
||||
import find = require('lodash/find');
|
||||
import {iRendererStore} from './iRenderer';
|
||||
import {SimpleMap} from '../utils/SimpleMap';
|
||||
|
||||
interface IOption {
|
||||
value?: string | number | null;
|
||||
|
@ -34,6 +29,7 @@ const ErrorDetail = types.model('ErrorDetail', {
|
|||
export const FormItemStore = types
|
||||
.model('FormItemStore', {
|
||||
identifier: types.identifier,
|
||||
isFocused: false,
|
||||
type: '',
|
||||
unique: false,
|
||||
loading: false,
|
||||
|
@ -81,41 +77,6 @@ export const FormItemStore = types
|
|||
return self.errorData.map(item => item.msg);
|
||||
}
|
||||
|
||||
// function selectedOptions(options:Array<Option>=(self.options as any).toJS()) {
|
||||
// return value2array(getValue(), {
|
||||
// multiple: self.multiple,
|
||||
// delimiter: self.delimiter,
|
||||
// valueField: self.valueField,
|
||||
// options: options
|
||||
// })
|
||||
// }
|
||||
|
||||
// function filteredOptions(data:object):Array<IOption> {
|
||||
// let options:Array<IOption> = self.options;
|
||||
// options = options.filter(item => {
|
||||
// let filtered = getExprProperties(item, data);
|
||||
// return filtered.visible !== false && !filtered.hidden;
|
||||
// });
|
||||
|
||||
// let parentStore = getForm().parentStore;
|
||||
// if (parentStore && parentStore.storeType === ComboStore.name) {
|
||||
// let combo = parentStore as IComboStore;
|
||||
// let group = combo.uniques.get(self.name) as IUniqueGroup;
|
||||
// let selectedOptions:Array<any> = [];
|
||||
// group && group.items.forEach(item => {
|
||||
// if (self !== item) {
|
||||
// selectedOptions.push(...item.selectedOptions().map(item => item.value))
|
||||
// }
|
||||
// });
|
||||
|
||||
// if (selectedOptions.length) {
|
||||
// options = options.filter(option => !~selectedOptions.indexOf(option.value))
|
||||
// }
|
||||
// }
|
||||
|
||||
// return options;
|
||||
// }
|
||||
|
||||
return {
|
||||
get form(): any {
|
||||
return getForm();
|
||||
|
@ -144,9 +105,6 @@ export const FormItemStore = types
|
|||
return getLastOptionValue();
|
||||
},
|
||||
|
||||
// selectedOptions,
|
||||
// filteredOptions,
|
||||
|
||||
getSelectedOptions(value: any = getValue()) {
|
||||
if (value === getValue()) {
|
||||
return self.selectedOptions;
|
||||
|
@ -212,6 +170,9 @@ export const FormItemStore = types
|
|||
})
|
||||
|
||||
.actions(self => {
|
||||
const form = self.form as IFormStore;
|
||||
const dialogCallbacks = new SimpleMap<(result?: any) => void>();
|
||||
|
||||
function config({
|
||||
required,
|
||||
unique,
|
||||
|
@ -241,8 +202,6 @@ export const FormItemStore = types
|
|||
type?: string;
|
||||
id?: string;
|
||||
}) {
|
||||
const form = self.form as IFormStore;
|
||||
|
||||
if (typeof rules === 'string') {
|
||||
rules = str2rules(rules);
|
||||
}
|
||||
|
@ -278,6 +237,14 @@ export const FormItemStore = types
|
|||
}
|
||||
}
|
||||
|
||||
function focus() {
|
||||
self.isFocused = true;
|
||||
}
|
||||
|
||||
function blur() {
|
||||
self.isFocused = false;
|
||||
}
|
||||
|
||||
function changeValue(value: any, isPrintine: boolean = false) {
|
||||
if (typeof value === 'undefined' || value === '__undefined') {
|
||||
self.form.deleteValueByName(self.name);
|
||||
|
@ -366,7 +333,7 @@ export const FormItemStore = types
|
|||
options?: fetchOptions,
|
||||
clearValue?: boolean,
|
||||
onChange?: (value: any) => void
|
||||
) => Promise<any> = flow(function* getInitData(
|
||||
) => Promise<Payload | null> = flow(function* getInitData(
|
||||
api: string,
|
||||
data: object,
|
||||
options?: fetchOptions,
|
||||
|
@ -442,8 +409,9 @@ export const FormItemStore = types
|
|||
console.error(e.stack);
|
||||
getRoot(self) &&
|
||||
(getRoot(self) as IRendererStore).notify('error', e.message);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} as any);
|
||||
|
||||
function syncOptions(originOptions?: Array<any>) {
|
||||
if (!self.options.length && typeof self.value === 'undefined') {
|
||||
|
@ -528,7 +496,7 @@ export const FormItemStore = types
|
|||
unMatched = {
|
||||
[self.valueField || 'value']: item,
|
||||
[self.labelField || 'label']: item,
|
||||
__unmatched: true
|
||||
'__unmatched': true
|
||||
};
|
||||
|
||||
const orgin: any =
|
||||
|
@ -595,27 +563,30 @@ export const FormItemStore = types
|
|||
clearError();
|
||||
}
|
||||
|
||||
function openDialog(schema: any, ctx: any, additonal?: object) {
|
||||
let proto = ctx.__super ? ctx.__super : self.form.data;
|
||||
|
||||
if (additonal) {
|
||||
proto = createObject(proto, additonal);
|
||||
}
|
||||
|
||||
const data = createObject(proto, {
|
||||
...ctx
|
||||
});
|
||||
|
||||
function openDialog(
|
||||
schema: any,
|
||||
data: any = form.data,
|
||||
callback?: (ret?: any) => void
|
||||
) {
|
||||
self.dialogSchema = schema;
|
||||
self.dialogData = data;
|
||||
self.dialogOpen = true;
|
||||
callback && dialogCallbacks.set(self.dialogData, callback);
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
function closeDialog(result?: any) {
|
||||
const callback = dialogCallbacks.get(self.dialogData);
|
||||
self.dialogOpen = false;
|
||||
|
||||
if (callback) {
|
||||
dialogCallbacks.delete(self.dialogData);
|
||||
setTimeout(() => callback(result), 200);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
focus,
|
||||
blur,
|
||||
config,
|
||||
changeValue,
|
||||
validate,
|
||||
|
|
|
@ -2,6 +2,7 @@ import {types, getRoot, Instance} from 'mobx-state-tree';
|
|||
import {extendObject, createObject} from '../utils/helper';
|
||||
import {IRendererStore} from './index';
|
||||
import {dataMapping} from '../utils/tpl-builtin';
|
||||
import {SimpleMap} from '../utils/SimpleMap';
|
||||
|
||||
export const iRendererStore = types
|
||||
.model('iRendererStore', {
|
||||
|
@ -32,7 +33,7 @@ export const iRendererStore = types
|
|||
};
|
||||
})
|
||||
.actions(self => {
|
||||
const dialogCallbacks = new Map();
|
||||
const dialogCallbacks = new SimpleMap<(result?: any) => void>();
|
||||
|
||||
return {
|
||||
initData(data: object = {}) {
|
||||
|
@ -97,10 +98,7 @@ export const iRendererStore = types
|
|||
self.dialogData = data;
|
||||
}
|
||||
self.dialogOpen = true;
|
||||
|
||||
if (callback) {
|
||||
dialogCallbacks.set(self.dialogData, callback);
|
||||
}
|
||||
callback && dialogCallbacks.set(self.dialogData, callback);
|
||||
},
|
||||
|
||||
closeDialog(result?: any) {
|
||||
|
|
Loading…
Reference in New Issue