commit
2529cfe18d
|
@ -146,6 +146,9 @@ $gap-base: px2rem(15px) !default;
|
||||||
$gap-md: px2rem(20px) !default;
|
$gap-md: px2rem(20px) !default;
|
||||||
$gap-lg: px2rem(30px) !default;
|
$gap-lg: px2rem(30px) !default;
|
||||||
|
|
||||||
|
$icon-color: $gray600 !default;
|
||||||
|
$icon-onHover-color: $gray900 !default;
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
$scrollbar-width: px2rem(17px) !default;
|
$scrollbar-width: px2rem(17px) !default;
|
||||||
|
|
||||||
|
@ -446,7 +449,7 @@ $Table-thead-fontSize: $fontSizeBase !default;
|
||||||
$Table-thead-color: $text--loud-color !default;
|
$Table-thead-color: $text--loud-color !default;
|
||||||
$Table-thead-borderColor: $Table-borderColor !default;
|
$Table-thead-borderColor: $Table-borderColor !default;
|
||||||
$Table-thead-borderWidth: $Table-borderWidth !default;
|
$Table-thead-borderWidth: $Table-borderWidth !default;
|
||||||
$Table-thead-iconColor: $text--muted-color !default;
|
$Table-thead-iconColor: $icon-color !default;
|
||||||
$TableCell-height: px2rem(40px) !default;
|
$TableCell-height: px2rem(40px) !default;
|
||||||
$TableCell-paddingX: $gap-sm !default;
|
$TableCell-paddingX: $gap-sm !default;
|
||||||
$TableCell--edge-paddingX: $gap-md !default;
|
$TableCell--edge-paddingX: $gap-md !default;
|
||||||
|
@ -554,19 +557,19 @@ $ListItem-onModified-borderColor: darken($ListItem-onModified-bg, 10%) !default;
|
||||||
$ListItem-onDragging-opacity: 0.1 !default;
|
$ListItem-onDragging-opacity: 0.1 !default;
|
||||||
|
|
||||||
// QuickEdit
|
// QuickEdit
|
||||||
$QuickEdit-iconColor: $text--muted-color !default;
|
$QuickEdit-iconColor: $icon-color !default;
|
||||||
$QuickEdit-onHover-iconColor: $text-color !default;
|
$QuickEdit-onHover-iconColor: $icon-onHover-color !default;
|
||||||
$QuickEdit-onFocus-borderColor: $info !default;
|
$QuickEdit-onFocus-borderColor: $info !default;
|
||||||
$QuickEdit-onFocus-borderWidth: $borderWidth !default;
|
$QuickEdit-onFocus-borderWidth: $borderWidth !default;
|
||||||
|
|
||||||
// Copyable
|
// Copyable
|
||||||
$Copyable-iconColor: $text--muted-color !default;
|
$Copyable-iconColor: $icon-color !default;
|
||||||
$Copyable-onHover-iconColor: $text-color !default;
|
$Copyable-onHover-iconColor: $icon-onHover-color !default;
|
||||||
|
|
||||||
// PopOverAble
|
// PopOverAble
|
||||||
|
|
||||||
$PopOverAble-iconColor: $text--muted-color !default;
|
$PopOverAble-iconColor: $icon-color !default;
|
||||||
$PopOverAble-onHover-iconColor: $text-color !default;
|
$PopOverAble-onHover-iconColor: $icon-onHover-color !default;
|
||||||
|
|
||||||
// PopOver
|
// PopOver
|
||||||
$PopOver-bg: white !default;
|
$PopOver-bg: white !default;
|
||||||
|
@ -574,8 +577,8 @@ $PopOver-bg: white !default;
|
||||||
// Remark
|
// Remark
|
||||||
$Remark-width: 1rem !default;
|
$Remark-width: 1rem !default;
|
||||||
$Remark-icon-fontSize: $fontSizeBase !default;
|
$Remark-icon-fontSize: $fontSizeBase !default;
|
||||||
$Remark-iconColor: $text--muted-color !default;
|
$Remark-iconColor: $icon-color !default;
|
||||||
$Remark-onHover-iconColor: $text-color !default;
|
$Remark-onHover-iconColor: $icon-onHover-color !default;
|
||||||
$Remark-bg: transparent !default;
|
$Remark-bg: transparent !default;
|
||||||
$Remark-onHover-bg: transparent !default;
|
$Remark-onHover-bg: transparent !default;
|
||||||
$Remark-borderWidth: 0 !default;
|
$Remark-borderWidth: 0 !default;
|
||||||
|
@ -618,7 +621,7 @@ $Form-input-onActive-color: $info !default;
|
||||||
$Form-input-borderRadius: $borderRadius !default;
|
$Form-input-borderRadius: $borderRadius !default;
|
||||||
$Form-input-borderColor: $borderColor !default;
|
$Form-input-borderColor: $borderColor !default;
|
||||||
$Form-input-borderWidth: px2rem(1px) !default;
|
$Form-input-borderWidth: px2rem(1px) !default;
|
||||||
$Form-input-iconColor: #999 !default;
|
$Form-input-iconColor: $icon-color !default;
|
||||||
$Form-input-onFocused-borderColor: $info !default;
|
$Form-input-onFocused-borderColor: $info !default;
|
||||||
$Form-input-onFocused-bg: $Form-input-bg !default;
|
$Form-input-onFocused-bg: $Form-input-bg !default;
|
||||||
$Form-input-onError-borderColor: $danger !default;
|
$Form-input-onError-borderColor: $danger !default;
|
||||||
|
@ -674,18 +677,18 @@ $Form-select-onFocused-color: $Form-select-color !default;
|
||||||
$Form-select-onFocused-borderColor: $Form-input-onFocused-borderColor !default;
|
$Form-select-onFocused-borderColor: $Form-input-onFocused-borderColor !default;
|
||||||
$Form-select-onError-borderColor: $Form-input-onError-borderColor !default;
|
$Form-select-onError-borderColor: $Form-input-onError-borderColor !default;
|
||||||
$Form-selectOption-height: $Form-input-height !default;
|
$Form-selectOption-height: $Form-input-height !default;
|
||||||
$Form-selectValue-color: #007eff !default;
|
$Form-selectValue-color: $info !default;
|
||||||
$Form-selectValue-bg: saturate(lighten($Form-selectValue-color, 40%), 2.5%) !default;
|
$Form-selectValue-bg: saturate(lighten($Form-selectValue-color, 40%), 2.5%) !default;
|
||||||
$Form-selectValue-borderColor: saturate(lighten($Form-selectValue-color, 30%), 2.5%) !default;
|
$Form-selectValue-borderColor: saturate(lighten($Form-selectValue-color, 30%), 2.5%) !default;
|
||||||
$Form-selectValue-fontSize: $fontSizeSm !default;
|
$Form-selectValue-fontSize: $fontSizeSm !default;
|
||||||
$Form-select-caret-vender: 'FontAwesome' !default;
|
$Form-select-caret-vender: 'FontAwesome' !default;
|
||||||
$Form-select-caret-icon: '\f0d7' !default;
|
$Form-select-caret-icon: '\f0d7' !default;
|
||||||
$Form-select-caret-fontSize: $fontSizeBase !default;
|
$Form-select-caret-fontSize: $fontSizeBase !default;
|
||||||
$Form-select-caret-iconColor: $Form-input-iconColor !default;
|
$Form-select-caret-iconColor: $icon-color !default;
|
||||||
$Form-select-caret-onHover-iconColor: $Form-input-iconColor !default;
|
$Form-select-caret-onHover-iconColor: $icon-onHover-color !default;
|
||||||
$Form-select-outer-borderWidth: px2rem(1px) !default;
|
$Form-select-outer-borderWidth: px2rem(1px) !default;
|
||||||
$Form-select-outer-boxShadow: none !default;
|
$Form-select-outer-boxShadow: none !default;
|
||||||
$Form-select-outer-top: 100% !default;
|
$Form-select-input-fontSize: $fontSizeSm !default;
|
||||||
$Form-select-menu-height: $Form-input-height !default;
|
$Form-select-menu-height: $Form-input-height !default;
|
||||||
$Form-select-menu-bg: $white !default;
|
$Form-select-menu-bg: $white !default;
|
||||||
$Form-select-menu-color: $Form-select-color !default;
|
$Form-select-menu-color: $Form-select-color !default;
|
||||||
|
@ -696,6 +699,8 @@ $Form-select-menu-onActive-bg: transparent !default;
|
||||||
$Form-select-menu-onDisabled-color: $text--muted-color !default;
|
$Form-select-menu-onDisabled-color: $text--muted-color !default;
|
||||||
$Form-select-menu-onDisabled-bg: transparent !default;
|
$Form-select-menu-onDisabled-bg: transparent !default;
|
||||||
$Form-select-checkall-bottomBorder: #eceff8 !default;
|
$Form-select-checkall-bottomBorder: #eceff8 !default;
|
||||||
|
$Form-select-popoverGap: 0 !default;
|
||||||
|
$Form-select-search-height: $Form-select-menu-height !default;
|
||||||
|
|
||||||
// InputGroup
|
// InputGroup
|
||||||
$InputGroup-height: $Form-input-height !default;
|
$InputGroup-height: $Form-input-height !default;
|
||||||
|
@ -713,8 +718,8 @@ $InputGroup-select-bg: $white !default;
|
||||||
$InputGroup-select-onFocused-bg: $white !default;
|
$InputGroup-select-onFocused-bg: $white !default;
|
||||||
$InputGroup-select-color: $Form-select-color !default;
|
$InputGroup-select-color: $Form-select-color !default;
|
||||||
$InputGroup-select-onFocused-color: $Form-select-onFocused-color !default;
|
$InputGroup-select-onFocused-color: $Form-select-onFocused-color !default;
|
||||||
$InputGroup-select-arrowColor: $Form-select-caret-iconColor !default;
|
$InputGroup-select-arrowColor: $icon-color !default;
|
||||||
$InputGroup-select-onFocused-arrowColor: $Form-select-caret-iconColor !default;
|
$InputGroup-select-onFocused-arrowColor: $icon-onHover-color !default;
|
||||||
$InputGroup-button-borderWidth: $borderWidth !default;
|
$InputGroup-button-borderWidth: $borderWidth !default;
|
||||||
$InputGroup-button-borderColor: $Form-input-borderColor !default;
|
$InputGroup-button-borderColor: $Form-input-borderColor !default;
|
||||||
$InputGroup-button-borderRadius: $borderRadius !default;
|
$InputGroup-button-borderRadius: $borderRadius !default;
|
||||||
|
@ -892,6 +897,8 @@ $Checkbox-color: $Form-input-borderColor !default;
|
||||||
$Checkbox-onHover-color: $info !default;
|
$Checkbox-onHover-color: $info !default;
|
||||||
|
|
||||||
$Checkbox--sm-size: px2rem(16px) !default;
|
$Checkbox--sm-size: px2rem(16px) !default;
|
||||||
|
$Checkbox--sm-inner-size: $Checkbox--sm-size/2 !default;
|
||||||
|
$Checkbox--sm--full-inner-size: px2rem(10px) !default;
|
||||||
$Checkbox-gb: #fff !default;
|
$Checkbox-gb: #fff !default;
|
||||||
$Checkbox-borderRadius: $borderRadius !default;
|
$Checkbox-borderRadius: $borderRadius !default;
|
||||||
|
|
||||||
|
@ -949,8 +956,8 @@ $DatePicker-fontSize: $Form-input-fontSize !default;
|
||||||
$DatePicker-paddingX: px2rem(12px) !default;
|
$DatePicker-paddingX: px2rem(12px) !default;
|
||||||
$DatePicker-paddingY: ($DatePicker-height - $DatePicker-lineHeight * $DatePicker-fontSize)/2 - $DatePicker-borderWidth !default;
|
$DatePicker-paddingY: ($DatePicker-height - $DatePicker-lineHeight * $DatePicker-fontSize)/2 - $DatePicker-borderWidth !default;
|
||||||
$DatePicker-placeholderColor: $Form-input-placeholderColor !default;
|
$DatePicker-placeholderColor: $Form-input-placeholderColor !default;
|
||||||
$DatePicker-iconColor: $gray600 !default;
|
$DatePicker-iconColor: $icon-color !default;
|
||||||
$DatePicker-onHover-iconColor: darken($DatePicker-iconColor, 10%) !default;
|
$DatePicker-onHover-iconColor: $icon-onHover-color !default;
|
||||||
$DatePicker-onFocused-borderColor: $Form-input-onFocused-borderColor !default;
|
$DatePicker-onFocused-borderColor: $Form-input-onFocused-borderColor !default;
|
||||||
$DatePicker-toggler-vendor: 'FontAwesome' !default;
|
$DatePicker-toggler-vendor: 'FontAwesome' !default;
|
||||||
$DatePicker-toggler-fontSize: $Form-fontSize !default;
|
$DatePicker-toggler-fontSize: $Form-fontSize !default;
|
||||||
|
@ -1041,7 +1048,7 @@ $ListControl-item-onDisabled-bg: $ListControl-item-bg !default;
|
||||||
// Combo
|
// Combo
|
||||||
$Combo-toolbarBtn-lineHeight: 1 !default;
|
$Combo-toolbarBtn-lineHeight: 1 !default;
|
||||||
$Combo-toolbarBtn-height: px2rem(20px) !default;
|
$Combo-toolbarBtn-height: px2rem(20px) !default;
|
||||||
$Combo-toolbarBtn-color: $Form-input-iconColor !default;
|
$Combo-toolbarBtn-color: $icon-color !default;
|
||||||
$Combo-toolbarBtn-paddingX: px2rem(5px) !default;
|
$Combo-toolbarBtn-paddingX: px2rem(5px) !default;
|
||||||
$Combo-toolbarBtn-paddingY: px2rem(2px) !default;
|
$Combo-toolbarBtn-paddingY: px2rem(2px) !default;
|
||||||
|
|
||||||
|
@ -1261,7 +1268,7 @@ $TransferSelect-heading-borderBottom: $borderWidth solid $borderColor !default;
|
||||||
// Tree
|
// Tree
|
||||||
$TreeSelect-popover-bg: #fff !default;
|
$TreeSelect-popover-bg: #fff !default;
|
||||||
$Tree-indent: px2rem(20px) !default;
|
$Tree-indent: px2rem(20px) !default;
|
||||||
$Tree-itemArrowWidth: px2rem(10px) !default;
|
$Tree-itemArrowWidth: px2rem(16px) !default;
|
||||||
$Tree-arrowVendor: 'FontAwesome' !default;
|
$Tree-arrowVendor: 'FontAwesome' !default;
|
||||||
$Tree-unfoldedArrowContent: '\f107' !default;
|
$Tree-unfoldedArrowContent: '\f107' !default;
|
||||||
$Tree-foldedArrowContent: '\f105' !default;
|
$Tree-foldedArrowContent: '\f105' !default;
|
||||||
|
@ -1271,7 +1278,10 @@ $Tree-leafIconVendor: 'FontAwesome' !default;
|
||||||
$Tree-leafIconContent: '\f15b' !default;
|
$Tree-leafIconContent: '\f15b' !default;
|
||||||
$Tree-folderIconVendor: 'FontAwesome' !default;
|
$Tree-folderIconVendor: 'FontAwesome' !default;
|
||||||
$Tree-folderIconContent: '\f07b' !default;
|
$Tree-folderIconContent: '\f07b' !default;
|
||||||
$Tree-itemText--onChecked-color: $Form-selectValue-color !default;
|
$Tree-itemLabel--onChecked-color: $Form-selectValue-color !default;
|
||||||
|
$Tree-itemHeight: px2rem(30px) !default;
|
||||||
|
$Tree-item-onHover-bg: rgba(0, 126, 255, 0.08) !default;
|
||||||
|
$Tree-inputHeight: $Form-input-height * 0.85 !default;
|
||||||
|
|
||||||
// IconPicker
|
// IconPicker
|
||||||
$IconPicker-tabs-bg: #f0f3f4 !default;
|
$IconPicker-tabs-bg: #f0f3f4 !default;
|
||||||
|
@ -1339,8 +1349,8 @@ $Carousel-imageTitle-bottom: px2rem(45px) !default;
|
||||||
$Carousel-imageDescription-bottom: px2rem(25px) !default;
|
$Carousel-imageDescription-bottom: px2rem(25px) !default;
|
||||||
|
|
||||||
// Picker
|
// Picker
|
||||||
$Picker-iconColor: $gray600 !default;
|
$Picker-iconColor: $icon-color !default;
|
||||||
$Picker-onHover-iconColor: darken($Picker-iconColor, 10%) !default;
|
$Picker-onHover-iconColor: $icon-onHover-color !default;
|
||||||
$Picker-btn-vendor: 'FontAwesome' !default;
|
$Picker-btn-vendor: 'FontAwesome' !default;
|
||||||
$Picker-btn-fontSize: $Form-fontSize !default;
|
$Picker-btn-fontSize: $Form-fontSize !default;
|
||||||
$Picker-btn-icon: '\f2d2' !default;
|
$Picker-btn-icon: '\f2d2' !default;
|
|
@ -8,12 +8,12 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover input:not(:disabled) + i {
|
&:hover input:not(:disabled)+i {
|
||||||
border-color: $Checkbox-onHover-color;
|
border-color: $Checkbox-onHover-color;
|
||||||
// box-shadow: 0 0 px2rem(1px) $Checkbox-onHover-color inset;
|
// box-shadow: 0 0 px2rem(1px) $Checkbox-onHover-color inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
> i {
|
>i {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
background: $Checkbox-gb;
|
background: $Checkbox-gb;
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
+ span {
|
+span {
|
||||||
margin-left: $Checkbox-gap;
|
margin-left: $Checkbox-gap;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
@ -39,6 +39,8 @@
|
||||||
height: 0px;
|
height: 0px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,19 +50,17 @@
|
||||||
input {
|
input {
|
||||||
margin-left: -$Checkbox-size;
|
margin-left: -$Checkbox-size;
|
||||||
|
|
||||||
&:checked + i {
|
&:checked+i {
|
||||||
border-color: $Checkbox-onHover-color;
|
border-color: $Checkbox-onHover-color;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
left: ($Checkbox-size - px2rem(2px) - $Checkbox-inner-size) / 2;
|
|
||||||
top: ($Checkbox-size - px2rem(2px) - $Checkbox-inner-size) / 2;
|
|
||||||
width: $Checkbox-inner-size;
|
width: $Checkbox-inner-size;
|
||||||
height: $Checkbox-inner-size;
|
height: $Checkbox-inner-size;
|
||||||
background: $Checkbox-onHover-color;
|
background: $Checkbox-onHover-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[disabled] + i {
|
&[disabled]+i {
|
||||||
border-color: lighten($Checkbox-color, 5%);
|
border-color: lighten($Checkbox-color, 5%);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|
||||||
|
@ -70,13 +70,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[disabled] + i + span {
|
&[disabled]+i+span {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
color: $text--muted-color;
|
color: $text--muted-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> i {
|
>i {
|
||||||
width: $Checkbox-size;
|
width: $Checkbox-size;
|
||||||
height: $Checkbox-size;
|
height: $Checkbox-size;
|
||||||
border: px2rem(1px) solid $Checkbox-color;
|
border: px2rem(1px) solid $Checkbox-color;
|
||||||
|
@ -87,25 +87,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&--full.#{$ns}Checkbox--checkbox {
|
&--full.#{$ns}Checkbox--checkbox {
|
||||||
&:not(:disabled) + i:hover {
|
&:not(:disabled)+i:hover {
|
||||||
border-color: $Checkbox-color;
|
border-color: $Checkbox-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
&:checked + i {
|
&:checked+i {
|
||||||
border-color: $Checkbox-onHover-color;
|
border-color: $Checkbox-onHover-color;
|
||||||
background: $Checkbox-onHover-color;
|
background: $Checkbox-onHover-color;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
// todo 后面自动计算
|
|
||||||
@if $ns== 'cxd-' {
|
|
||||||
top: px2rem(2px);
|
|
||||||
left: px2rem(1px);
|
|
||||||
} @else {
|
|
||||||
top: px2rem(5px);
|
|
||||||
left: ($Checkbox-size - $Checkbox--full-inner-size) /2;
|
|
||||||
}
|
|
||||||
|
|
||||||
width: $Checkbox--full-inner-size;
|
width: $Checkbox--full-inner-size;
|
||||||
height: $Checkbox--full-inner-size / 2;
|
height: $Checkbox--full-inner-size / 2;
|
||||||
border-color: $white;
|
border-color: $white;
|
||||||
|
@ -120,13 +111,13 @@
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
&:checked[disabled] + i {
|
&:checked[disabled]+i {
|
||||||
border-color: lighten($Checkbox-color, 5%);
|
border-color: lighten($Checkbox-color, 5%);
|
||||||
background-color: lighten($Checkbox-color, 5%);
|
background-color: lighten($Checkbox-color, 5%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> i {
|
>i {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
@ -138,9 +129,9 @@
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
transition: all 0.2s;
|
transition: width 0.2s, height 0.2s, transform 0.2s;
|
||||||
border-width: 0 0 px2rem(2px) px2rem(2px);
|
border-width: 0 0 px2rem(2px) px2rem(2px);
|
||||||
transform: rotate(-40deg);
|
transform: translate(-50%, -60%) rotate(-40deg);
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,12 +143,10 @@
|
||||||
input {
|
input {
|
||||||
margin-left: -$Radio-size;
|
margin-left: -$Radio-size;
|
||||||
|
|
||||||
&:checked + i {
|
&:checked+i {
|
||||||
border-color: $Radio-onHover-color;
|
border-color: $Radio-onHover-color;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
left: ($Radio-size - px2rem(2px) - $Radio-inner-size) / 2;
|
|
||||||
top: ($Radio-size - px2rem(2px) - $Radio-inner-size) / 2;
|
|
||||||
width: $Radio-inner-size;
|
width: $Radio-inner-size;
|
||||||
height: $Radio-inner-size;
|
height: $Radio-inner-size;
|
||||||
background-color: $Radio-onHover-color;
|
background-color: $Radio-onHover-color;
|
||||||
|
@ -165,7 +154,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[disabled] + i {
|
&[disabled]+i {
|
||||||
border-color: lighten($Radio-color, 5%);
|
border-color: lighten($Radio-color, 5%);
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|
||||||
|
@ -174,13 +163,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[disabled] + i + span {
|
&[disabled]+i+span {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
color: $text--muted-color;
|
color: $text--muted-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> i {
|
>i {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: $Radio-size;
|
width: $Radio-size;
|
||||||
height: $Radio-size;
|
height: $Radio-size;
|
||||||
|
@ -192,30 +181,41 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&--sm {
|
&--sm {
|
||||||
margin-right: px2rem(5px);
|
padding-left: $Checkbox--sm-size;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
&:checked + i {
|
margin-left: -$Checkbox--sm-size;
|
||||||
|
|
||||||
|
&:checked+i {
|
||||||
&:before {
|
&:before {
|
||||||
left: $Checkbox--sm-size / 4 - px2rem(1px);
|
width: $Checkbox--sm-inner-size;
|
||||||
top: $Checkbox--sm-size / 4 - px2rem(1px);
|
height: $Checkbox--sm-inner-size;
|
||||||
width: $Checkbox--sm-size / 2;
|
|
||||||
height: $Checkbox--sm-size / 2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> i {
|
>i {
|
||||||
width: $Checkbox--sm-size;
|
width: $Checkbox--sm-size;
|
||||||
height: $Checkbox--sm-size;
|
height: $Checkbox--sm-size;
|
||||||
margin-left: -$Checkbox--sm-size;
|
margin-left: -$Checkbox--sm-size;
|
||||||
|
|
||||||
+ span {
|
+span {
|
||||||
margin-left: $gap-xs;
|
margin-left: $gap-xs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--sm.#{$ns}Checkbox--full {
|
||||||
|
input {
|
||||||
|
&:checked+i {
|
||||||
|
&:before {
|
||||||
|
width: $Checkbox--sm--full-inner-size;
|
||||||
|
height: $Checkbox--sm--full-inner-size / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-desc {
|
&-desc {
|
||||||
color: $text--muted-color;
|
color: $text--muted-color;
|
||||||
margin-left: $Checkbox-gap;
|
margin-left: $Checkbox-gap;
|
||||||
|
@ -244,13 +244,14 @@
|
||||||
|
|
||||||
.#{$ns}RadiosControl-group,
|
.#{$ns}RadiosControl-group,
|
||||||
.#{$ns}CheckboxesControl-group {
|
.#{$ns}CheckboxesControl-group {
|
||||||
|
|
||||||
.#{$ns}RadiosControl-group,
|
.#{$ns}RadiosControl-group,
|
||||||
.#{$ns}CheckboxesControl-group {
|
.#{$ns}CheckboxesControl-group {
|
||||||
padding-left: px2rem(80px);
|
padding-left: px2rem(80px);
|
||||||
@include clearfix();
|
@include clearfix();
|
||||||
|
|
||||||
> .#{$ns}RadiosControl-groupLabel,
|
>.#{$ns}RadiosControl-groupLabel,
|
||||||
> .#{$ns}CheckboxesControl-groupLabel {
|
>.#{$ns}CheckboxesControl-groupLabel {
|
||||||
float: left;
|
float: left;
|
||||||
width: px2rem(80px);
|
width: px2rem(80px);
|
||||||
margin-left: px2rem(-80px);
|
margin-left: px2rem(-80px);
|
||||||
|
@ -261,4 +262,4 @@
|
||||||
.#{$ns}RadiosControl-groupLabel,
|
.#{$ns}RadiosControl-groupLabel,
|
||||||
.#{$ns}CheckboxesControl-groupLabel {
|
.#{$ns}CheckboxesControl-groupLabel {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
|
@ -8,10 +8,7 @@
|
||||||
background: $Form-select-bg;
|
background: $Form-select-bg;
|
||||||
border-radius: $Form-select-borderRadius;
|
border-radius: $Form-select-borderRadius;
|
||||||
height: $Form-selectOption-height;
|
height: $Form-selectOption-height;
|
||||||
$paddingY: (
|
$paddingY: ($Form-selectOption-height - $Form-input-lineHeight * $Form-input-fontSize - $Form-select-borderWidth * 2)/2;
|
||||||
$Form-selectOption-height - $Form-input-lineHeight *
|
|
||||||
$Form-input-fontSize - $Form-select-borderWidth * 2
|
|
||||||
)/2;
|
|
||||||
padding: $paddingY 0 $paddingY $Form-select-paddingX;
|
padding: $paddingY 0 $paddingY $Form-select-paddingX;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: $Form-select-color;
|
color: $Form-select-color;
|
||||||
|
@ -46,17 +43,7 @@
|
||||||
user-select: none;
|
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 {
|
&-value {
|
||||||
line-height: $Form-input-lineHeight * $Form-input-fontSize;
|
line-height: $Form-input-lineHeight * $Form-input-fontSize;
|
||||||
|
@ -64,6 +51,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&--searchable {
|
&--searchable {
|
||||||
|
|
||||||
.#{$ns}Select-placeholder,
|
.#{$ns}Select-placeholder,
|
||||||
.#{$ns}Select-value {
|
.#{$ns}Select-value {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -79,22 +67,21 @@
|
||||||
.#{$ns}Select-valueWrap {
|
.#{$ns}Select-valueWrap {
|
||||||
margin-bottom: -$gap-xs;
|
margin-bottom: -$gap-xs;
|
||||||
|
|
||||||
> input {
|
>input {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: px2rem(100px);
|
width: px2rem(100px);
|
||||||
margin-bottom: $gap-xs;
|
margin-bottom: $gap-xs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}Select-values + .#{$ns}Select-input {
|
.#{$ns}Select-values+.#{$ns}Select-input {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}Select-value {
|
.#{$ns}Select-value {
|
||||||
position: static;
|
position: static;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
line-height: $Form-input-lineHeight * $Form-input-fontSize -
|
line-height: $Form-input-lineHeight * $Form-input-fontSize - px2rem(2px);
|
||||||
px2rem(2px);
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-size: $Form-selectValue-fontSize;
|
font-size: $Form-selectValue-fontSize;
|
||||||
|
@ -145,44 +132,50 @@
|
||||||
transform: rotate(180deg);
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-menu {
|
&-menu {
|
||||||
max-height: px2rem(300px);
|
max-height: px2rem(300px);
|
||||||
overflow: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-checkAll {
|
&-input {
|
||||||
padding: (
|
cursor: pointer;
|
||||||
$Form-select-menu-height - $Form-input-lineHeight *
|
outline: none;
|
||||||
$Form-input-fontSize - px2rem(2px)
|
border: none;
|
||||||
)/2 $Form-select-paddingX;
|
margin: 0 $Form-select-paddingX;
|
||||||
border-bottom: px2rem(1px) solid $Form-select-checkall-bottomBorder;
|
height: $Form-select-search-height;
|
||||||
min-width: px2rem(100px);
|
font-size: $Form-select-input-fontSize;
|
||||||
|
border-bottom: 1px solid $borderColor;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
label {
|
// &.is-focused {
|
||||||
display: block;
|
// border-color: $Form-input-onFocused-borderColor;
|
||||||
|
// }
|
||||||
|
|
||||||
|
>svg {
|
||||||
|
fill: #999;
|
||||||
|
width: px2rem(14px);
|
||||||
|
min-width: px2rem(14px);
|
||||||
|
height: px2rem(14px);
|
||||||
|
margin-right: px2rem(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
>input {
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
flex-grow: 1;
|
||||||
|
background: transparent;
|
||||||
|
position: relative;
|
||||||
|
top: 0.125em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-option {
|
&-option {
|
||||||
padding: (
|
cursor: pointer;
|
||||||
$Form-select-menu-height - $Form-input-lineHeight *
|
min-width: px2rem(150px);
|
||||||
$Form-input-fontSize - px2rem(2px)
|
padding: ($Form-select-menu-height - $Form-input-lineHeight * $Form-input-fontSize)/2 $Form-select-paddingX;
|
||||||
)/2 $Form-select-paddingX;
|
|
||||||
|
|
||||||
&.is-active {
|
&.is-active {
|
||||||
color: $Form-select-menu-onActive-color;
|
color: $Form-select-menu-onActive-color;
|
||||||
|
@ -202,12 +195,50 @@
|
||||||
&--placeholder {
|
&--placeholder {
|
||||||
color: $Form-input-placeholderColor;
|
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;
|
||||||
|
font-size: $Form-select-input-fontSize;
|
||||||
|
user-select: none;
|
||||||
|
padding: ($Form-select-menu-height - $Form-input-lineHeight * $Form-input-fontSize)/2 $Form-select-paddingX;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-option-hl {
|
&-option-hl {
|
||||||
color: $red;
|
color: $red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-addBtn {
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: ($Form-select-menu-height - $Form-input-lineHeight * $Form-input-fontSize)/2 $Form-select-paddingX;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
>svg {
|
||||||
|
width: px2rem(14px);
|
||||||
|
height: px2rem(14px);
|
||||||
|
margin-right: $Checkbox-gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.is-focused,
|
&.is-focused,
|
||||||
&.is-opened {
|
&.is-opened {
|
||||||
border-color: $Form-input-onFocused-borderColor;
|
border-color: $Form-input-onFocused-borderColor;
|
||||||
|
@ -243,28 +274,34 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}Select-popover {
|
.#{$ns}Select-popover {
|
||||||
margin-top: -$Form-select-borderWidth;
|
margin-top: $Form-select-popoverGap - $Form-select-outer-borderWidth;
|
||||||
|
|
||||||
background: $Form-select-menu-bg;
|
background: $Form-select-menu-bg;
|
||||||
color: $Form-select-menu-color;
|
color: $Form-select-menu-color;
|
||||||
border: $Form-select-outer-borderWidth solid
|
border: $Form-select-outer-borderWidth solid $Form-input-onFocused-borderColor;
|
||||||
$Form-input-onFocused-borderColor;
|
|
||||||
box-shadow: $Form-select-outer-boxShadow;
|
box-shadow: $Form-select-outer-boxShadow;
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
min-width: px2rem(100px);
|
min-width: px2rem(100px);
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
&.#{$ns}PopOver--leftTopLeftBottom {
|
||||||
|
margin-top: -($Form-select-popoverGap - $Form-select-outer-borderWidth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}SelectControl:not(.is-inline) > .#{$ns}Select {
|
.#{$ns}SelectControl:not(.is-inline)>.#{$ns}Select {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 需要能撑开
|
// 需要能撑开
|
||||||
@include media-breakpoint-up(sm) {
|
@include media-breakpoint-up(sm) {
|
||||||
.#{$ns}Form-control--sizeXs > .#{$ns}Select,
|
|
||||||
.#{$ns}Form-control--sizeSm > .#{$ns}Select,
|
.#{$ns}Form-control--sizeXs>.#{$ns}Select,
|
||||||
.#{$ns}Form-control--sizeMd > .#{$ns}Select,
|
.#{$ns}Form-control--sizeSm>.#{$ns}Select,
|
||||||
.#{$ns}Form-control--sizeLg > .#{$ns}Select {
|
.#{$ns}Form-control--sizeMd>.#{$ns}Select,
|
||||||
|
.#{$ns}Form-control--sizeLg>.#{$ns}Select {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
display: inline-flex !important;
|
display: inline-flex !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,34 +1,3 @@
|
||||||
@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 {
|
.#{$ns}TreeControl {
|
||||||
border: 1px solid $Form-input-borderColor;
|
border: 1px solid $Form-input-borderColor;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
|
@ -46,6 +15,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}Tree {
|
.#{$ns}Tree {
|
||||||
|
|
||||||
&-list,
|
&-list,
|
||||||
&-sublist {
|
&-sublist {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
@ -53,39 +23,24 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-sublist {
|
&-sublist.is-folded {
|
||||||
padding-left: $Tree-indent;
|
display: none;
|
||||||
|
|
||||||
&.is-folded {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
> li {
|
|
||||||
@include tree-input;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
line-height: px2rem(30px);
|
line-height: $Tree-itemHeight;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.#{$ns}Tree-item-icons {
|
>div {
|
||||||
visibility: hidden;
|
|
||||||
transition: visibility .1s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
> a {
|
|
||||||
color: inherit;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
> span.#{$ns}Tree-item-icons {
|
>.#{$ns}Tree-item-icons {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> span > svg {
|
>span>svg {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -95,49 +50,114 @@
|
||||||
margin-left: px2rem(5px);
|
margin-left: px2rem(5px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&--isLeaf > a {
|
|
||||||
padding-left: $Tree-itemArrowWidth + $gap-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--isEdit {
|
|
||||||
display: inline-block;
|
|
||||||
@include tree-input;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-rootItem {
|
&-rootItem {
|
||||||
> a > i {
|
line-height: $Tree-itemHeight;
|
||||||
margin-left: 0 !important;
|
}
|
||||||
|
|
||||||
|
&-item>div:hover>.#{$ns}Tree-item-icons,
|
||||||
|
&-rootItem>div:hover>.#{$ns}Tree-item-icons {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-itemLabel {
|
||||||
|
&:hover {
|
||||||
|
background: $Tree-item-onHover-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item-icons {
|
||||||
|
visibility: hidden;
|
||||||
|
transition: visibility .1s ease;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
height: $Tree-itemHeight;
|
||||||
|
line-height: $Tree-itemHeight;
|
||||||
|
|
||||||
|
>a {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: $gap-xs;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
>svg {
|
||||||
|
$svgSize: px2rem(16px);
|
||||||
|
width: $svgSize;
|
||||||
|
height: $svgSize;
|
||||||
|
top: 0.125 * $svgSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-itemInput {
|
||||||
|
padding-left: $Tree-itemArrowWidth;
|
||||||
|
|
||||||
|
>a {
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: $gap-sm;
|
||||||
|
color: $icon-color;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $icon-onHover-color;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}Tree-addTop {
|
>input {
|
||||||
height: px2rem(25px);
|
outline: none;
|
||||||
line-height: px2rem(25px);
|
background-color: $Form-input-bg;
|
||||||
cursor: pointer;
|
border: $Form-input-borderWidth solid $Form-input-borderColor;
|
||||||
padding-left: $Tree-indent;
|
border-radius: $Form-input-borderRadius;
|
||||||
> p {
|
line-height: $Form-input-lineHeight;
|
||||||
> svg {
|
padding: ($Tree-inputHeight - $Form-input-lineHeight * $Form-input-fontSize - px2rem(2px))/2 $Form-input-paddingX;
|
||||||
position: relative;
|
font-size: $Form-input-fontSize;
|
||||||
top: 2px;
|
|
||||||
width: px2rem(16px);
|
&::placeholder {
|
||||||
height: px2rem(16px);
|
color: $Form-input-placeholderColor;
|
||||||
}
|
user-select: none;
|
||||||
> span {
|
|
||||||
padding-left: px2rem(5px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-input {
|
&:focus {
|
||||||
@include tree-input
|
border-color: $Form-input-onFocused-borderColor;
|
||||||
|
box-shadow: $Form-input-boxShadow;
|
||||||
|
|
||||||
|
@if $Form-input-onFocused-bg !=$Form-input-bg {
|
||||||
|
background-color: $Form-input-onFocused-bg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-addTopBtn {
|
||||||
|
cursor: pointer;
|
||||||
|
height: $Tree-itemHeight;
|
||||||
|
line-height: $Tree-itemHeight;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
color: $text--muted-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
>svg {
|
||||||
|
$svgSize: px2rem(14px);
|
||||||
|
width: $svgSize;
|
||||||
|
height: $svgSize;
|
||||||
|
top: $svgSize * 0.125;
|
||||||
|
margin-right: $Tree-itemArrowWidth - px2rem(14px); // icon 的宽度是14px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-itemArrow {
|
&-itemArrow {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: $Tree-itemArrowWidth;
|
width: $Tree-itemArrowWidth;
|
||||||
margin-right: $gap-xs;
|
text-align: center;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
|
@ -151,9 +171,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-itemArrowPlaceholder {
|
||||||
|
display: inline-block;
|
||||||
|
width: $Tree-itemArrowWidth;
|
||||||
|
}
|
||||||
|
|
||||||
&-itemIcon {
|
&-itemIcon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: $gap-xs;
|
margin-right: px2rem(3px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-rootIcon {
|
&-rootIcon {
|
||||||
|
@ -180,13 +205,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-itemText {
|
&-itemLabel {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&.is-checked,
|
&.is-checked,
|
||||||
&.is-children-checked {
|
&.is-children-checked {
|
||||||
color: $Tree-itemText--onChecked-color;
|
color: $Tree-itemLabel--onChecked-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-disabled {
|
&.is-disabled {
|
||||||
|
@ -194,7 +218,55 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-itemText {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
&-placeholder {
|
&-placeholder {
|
||||||
color: $text--muted-color;
|
color: $text--muted-color;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
&-item &-item>&-itemLabel,
|
||||||
|
&-item>&-placeholder {
|
||||||
|
padding-left: $Tree-indent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item &-item &-item>&-itemLabel,
|
||||||
|
&-item &-item>&-placeholder {
|
||||||
|
padding-left: $Tree-indent * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item &-item &-item &-item>&-itemLabel {
|
||||||
|
padding-left: $Tree-indent * 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item &-item &-item &-item &-item>&-itemLabel {
|
||||||
|
padding-left: $Tree-indent * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item &-item &-item &-item &-item &-item>&-itemLabel {
|
||||||
|
padding-left: $Tree-indent * 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item &-item &-item &-item &-item &-item &-item>&-itemLabel {
|
||||||
|
padding-left: $Tree-indent * 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item &-item &-item &-item &-item &-item &-item &-item>&-itemLabel {
|
||||||
|
padding-left: $Tree-indent * 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item &-item &-item &-item &-item &-item &-item &-item &-item>&-itemLabel {
|
||||||
|
padding-left: $Tree-indent * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item &-item &-item &-item &-item &-item &-item &-item &-item &-item>&-itemLabel {
|
||||||
|
padding-left: $Tree-indent * 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item &-item &-item &-item &-item &-item &-item &-item &-item &-item &-item>&-itemLabel {
|
||||||
|
padding-left: $Tree-indent * 10;
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,9 @@ $borderColor: #e8ebee;
|
||||||
|
|
||||||
$link-onHover-decoration: none;
|
$link-onHover-decoration: none;
|
||||||
|
|
||||||
|
$icon-color: #999;
|
||||||
|
$icon-onHover-color: $primary;
|
||||||
|
|
||||||
$Layout-header-boxShadow: none;
|
$Layout-header-boxShadow: none;
|
||||||
$Layout-header-bg: #F5F5F5;
|
$Layout-header-bg: #F5F5F5;
|
||||||
$Layout-aside-width: px2rem(180px);
|
$Layout-aside-width: px2rem(180px);
|
||||||
|
@ -102,6 +105,10 @@ $Form-select-outer-boxShadow: px2rem(2px) px2rem(4px) px2rem(8px) rgba(0, 0, 0,
|
||||||
$Form-select-menu-color: #333;
|
$Form-select-menu-color: #333;
|
||||||
$Form-select-menu-onHover-color: #000;
|
$Form-select-menu-onHover-color: #000;
|
||||||
$Form-select-menu-onHover-bg: #eaf6fe;
|
$Form-select-menu-onHover-bg: #eaf6fe;
|
||||||
|
$Form-select-menu-height: px2rem(24px);
|
||||||
|
$Form-select-popoverGap: px2rem(3px);
|
||||||
|
$Form-select-search-height: px2rem(30px);
|
||||||
|
$Form-selectValue-color: $primary !default;
|
||||||
|
|
||||||
$InputGroup-select-borderWidth: px2rem(1px);
|
$InputGroup-select-borderWidth: px2rem(1px);
|
||||||
$InputGroup-select-bg: #f6f7fb;
|
$InputGroup-select-bg: #f6f7fb;
|
||||||
|
|
|
@ -20,7 +20,7 @@ interface CheckboxProps {
|
||||||
id?: string;
|
id?: string;
|
||||||
key?: string | number;
|
key?: string | number;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
type?: string;
|
type: 'checkbox' | 'radio';
|
||||||
size?: 'sm' | 'lg' | 'small' | 'large';
|
size?: 'sm' | 'lg' | 'small' | 'large';
|
||||||
label?: string;
|
label?: string;
|
||||||
labelClassName?: string;
|
labelClassName?: string;
|
||||||
|
@ -80,19 +80,12 @@ export class Checkbox extends React.Component<CheckboxProps, any> {
|
||||||
labelClassName
|
labelClassName
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
className =
|
|
||||||
(className ? className : '') +
|
|
||||||
(size && sizeMap[size] ? ` ${sizeMap[size]}` : '');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
className={cx(
|
className={cx(`Checkbox Checkbox--${type}`, className, {
|
||||||
`Checkbox Checkbox--${type}`,
|
'Checkbox--full': !partial,
|
||||||
{
|
[`Checkbox--${size}`]: size
|
||||||
'Checkbox--full': !partial
|
})}
|
||||||
},
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
|
|
|
@ -49,6 +49,9 @@ interface OverlayProps {
|
||||||
target?: React.ReactNode | Function;
|
target?: React.ReactNode | Function;
|
||||||
}
|
}
|
||||||
export default class Overlay extends React.Component<OverlayProps> {
|
export default class Overlay extends React.Component<OverlayProps> {
|
||||||
|
static defaultProps = {
|
||||||
|
placement: 'auto'
|
||||||
|
};
|
||||||
constructor(props: OverlayProps) {
|
constructor(props: OverlayProps) {
|
||||||
super(props as any);
|
super(props as any);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ export class PopOver extends React.PureComponent<PopOverPorps, PopOverState> {
|
||||||
y: 0
|
y: 0
|
||||||
},
|
},
|
||||||
overlay: false,
|
overlay: false,
|
||||||
placement: 'bottom'
|
placement: 'auto'
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
|
|
@ -11,8 +11,8 @@ import 'react-datetime/css/react-datetime.css';
|
||||||
import Overlay from './Overlay';
|
import Overlay from './Overlay';
|
||||||
import PopOver from './PopOver';
|
import PopOver from './PopOver';
|
||||||
import Downshift, {ControllerStateAndHelpers} from 'downshift';
|
import Downshift, {ControllerStateAndHelpers} from 'downshift';
|
||||||
import cx from 'classnames';
|
|
||||||
import {closeIcon, Icon} from './icons';
|
import {closeIcon, Icon} from './icons';
|
||||||
|
// @ts-ignore
|
||||||
import matchSorter from 'match-sorter';
|
import matchSorter from 'match-sorter';
|
||||||
import {noop} from '../utils/helper';
|
import {noop} from '../utils/helper';
|
||||||
import find = require('lodash/find');
|
import find = require('lodash/find');
|
||||||
|
@ -46,6 +46,18 @@ export interface OptionProps {
|
||||||
clearable?: boolean;
|
clearable?: boolean;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
autoFill?: {[propName: string]: any};
|
autoFill?: {[propName: string]: any};
|
||||||
|
creatable?: boolean;
|
||||||
|
onAdd?: (
|
||||||
|
idx?: number | Array<number>,
|
||||||
|
value?: any,
|
||||||
|
skipForm?: boolean
|
||||||
|
) => void;
|
||||||
|
addControls?: Array<any>;
|
||||||
|
editable?: boolean;
|
||||||
|
editControls?: Array<any>;
|
||||||
|
onEdit?: (value: Option, origin?: Option, skipForm?: boolean) => void;
|
||||||
|
removable?: boolean;
|
||||||
|
onDelete?: (value: Option) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OptionValue = string | number | null | undefined | Option;
|
export type OptionValue = string | number | null | undefined | Option;
|
||||||
|
@ -143,11 +155,14 @@ export function normalizeOptions(
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SelectProps {
|
const DownshiftChangeTypes = Downshift.stateChangeTypes;
|
||||||
|
|
||||||
|
interface SelectProps extends OptionProps {
|
||||||
classPrefix: string;
|
classPrefix: string;
|
||||||
classnames: ClassNamesFn;
|
classnames: ClassNamesFn;
|
||||||
className?: string;
|
className?: string;
|
||||||
creatable: boolean;
|
creatable: boolean;
|
||||||
|
createBtnLabel: string;
|
||||||
multiple: boolean;
|
multiple: boolean;
|
||||||
valueField: string;
|
valueField: string;
|
||||||
labelField: string;
|
labelField: string;
|
||||||
|
@ -167,9 +182,7 @@ interface SelectProps {
|
||||||
inline: boolean;
|
inline: boolean;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
popOverContainer?: any;
|
popOverContainer?: any;
|
||||||
promptTextCreator: (label: string) => string;
|
|
||||||
onChange: (value: void | string | Option | Array<Option>) => void;
|
onChange: (value: void | string | Option | Array<Option>) => void;
|
||||||
onNewOptionClick: (value: Option) => void;
|
|
||||||
onFocus?: Function;
|
onFocus?: Function;
|
||||||
onBlur?: Function;
|
onBlur?: Function;
|
||||||
checkAll?: boolean;
|
checkAll?: boolean;
|
||||||
|
@ -191,17 +204,16 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
multiple: false,
|
multiple: false,
|
||||||
clearable: true,
|
clearable: true,
|
||||||
creatable: false,
|
creatable: false,
|
||||||
|
createBtnLabel: '新增选项',
|
||||||
searchPromptText: '输入内容进行检索',
|
searchPromptText: '输入内容进行检索',
|
||||||
loadingPlaceholder: '加载中..',
|
loadingPlaceholder: '加载中..',
|
||||||
noResultsText: '没有结果',
|
noResultsText: '未找到任何结果',
|
||||||
clearAllText: '移除所有',
|
clearAllText: '移除所有',
|
||||||
clearValueText: '移除',
|
clearValueText: '移除',
|
||||||
placeholder: '请选择',
|
placeholder: '请选择',
|
||||||
valueField: 'value',
|
valueField: 'value',
|
||||||
labelField: 'label',
|
labelField: 'label',
|
||||||
spinnerClassName: 'fa fa-spinner fa-spin fa-1x fa-fw',
|
spinnerClassName: 'fa fa-spinner fa-spin fa-1x fa-fw',
|
||||||
promptTextCreator: (label: string) => `新增:${label}`,
|
|
||||||
onNewOptionClick: noop,
|
|
||||||
inline: false,
|
inline: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
checkAll: false,
|
checkAll: false,
|
||||||
|
@ -229,6 +241,9 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
this.handleKeyPress = this.handleKeyPress.bind(this);
|
this.handleKeyPress = this.handleKeyPress.bind(this);
|
||||||
this.getTarget = this.getTarget.bind(this);
|
this.getTarget = this.getTarget.bind(this);
|
||||||
this.toggleCheckAll = this.toggleCheckAll.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 = {
|
this.state = {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
|
@ -244,22 +259,22 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
loadOptions,
|
loadOptions,
|
||||||
options,
|
options,
|
||||||
multiple,
|
multiple,
|
||||||
checkAll,
|
|
||||||
defaultCheckAll,
|
defaultCheckAll,
|
||||||
onChange,
|
onChange,
|
||||||
simpleValue
|
simpleValue
|
||||||
} = this.props;
|
} = this.props;
|
||||||
let {selection} = this.state;
|
let {selection} = this.state;
|
||||||
|
|
||||||
if (multiple && checkAll && defaultCheckAll && options.length) {
|
if (multiple && defaultCheckAll && options.length) {
|
||||||
selection = union(options, selection);
|
selection = union(options, selection);
|
||||||
this.setState(
|
this.setState({
|
||||||
{
|
selection: selection
|
||||||
selection: selection
|
});
|
||||||
},
|
|
||||||
() =>
|
// 因为等 State 设置完后再 onChange,会让 form 再 didMount 中的
|
||||||
onChange(simpleValue ? selection.map(item => item.value) : selection)
|
// onInit 出去的数据没有包含这部分,所以从 state 回调中拿出来了
|
||||||
);
|
// 存在风险
|
||||||
|
onChange(simpleValue ? selection.map(item => item.value) : selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOptions && loadOptions('');
|
loadOptions && loadOptions('');
|
||||||
|
@ -280,9 +295,13 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
|
|
||||||
open() {
|
open() {
|
||||||
this.props.disabled ||
|
this.props.disabled ||
|
||||||
this.setState({
|
this.setState(
|
||||||
isOpen: true
|
{
|
||||||
});
|
isOpen: true,
|
||||||
|
highlightedIndex: -1
|
||||||
|
},
|
||||||
|
() => setTimeout(this.focus, 500)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
|
@ -301,9 +320,13 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.disabled ||
|
this.props.disabled ||
|
||||||
this.setState({
|
this.setState(
|
||||||
isOpen: !this.state.isOpen
|
{
|
||||||
});
|
isOpen: !this.state.isOpen,
|
||||||
|
highlightedIndex: -1
|
||||||
|
},
|
||||||
|
this.state.isOpen ? undefined : () => setTimeout(this.focus, 500)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFocus(e: any) {
|
onFocus(e: any) {
|
||||||
|
@ -389,14 +412,9 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(selectItem: any) {
|
handleChange(selectItem: any) {
|
||||||
const {onChange, multiple, onNewOptionClick, simpleValue} = this.props;
|
const {onChange, multiple, simpleValue} = this.props;
|
||||||
let {selection} = this.state;
|
let {selection} = this.state;
|
||||||
|
|
||||||
if (selectItem.isNew) {
|
|
||||||
delete selectItem.isNew;
|
|
||||||
onNewOptionClick(selectItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (multiple) {
|
if (multiple) {
|
||||||
selection = selection.concat();
|
selection = selection.concat();
|
||||||
const idx = selection.indexOf(selectItem);
|
const idx = selection.indexOf(selectItem);
|
||||||
|
@ -417,27 +435,26 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
const loadOptions = this.props.loadOptions;
|
const loadOptions = this.props.loadOptions;
|
||||||
let doLoad = false;
|
let doLoad = false;
|
||||||
|
|
||||||
if (changes.isOpen !== void 0) {
|
|
||||||
update.isOpen = changes.isOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changes.highlightedIndex !== void 0) {
|
|
||||||
update.highlightedIndex = changes.highlightedIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (changes.type) {
|
switch (changes.type) {
|
||||||
case Downshift.stateChangeTypes.keyDownEnter:
|
case DownshiftChangeTypes.keyDownEnter:
|
||||||
case Downshift.stateChangeTypes.clickItem:
|
case DownshiftChangeTypes.clickItem:
|
||||||
update = {
|
update = {
|
||||||
...update,
|
...update,
|
||||||
inputValue: '',
|
inputValue: '',
|
||||||
isOpen: multiple && checkAll ? true : false,
|
isOpen: multiple ? true : false,
|
||||||
isFocused: multiple && checkAll ? true : false
|
isFocused: multiple && checkAll ? true : false
|
||||||
};
|
};
|
||||||
doLoad = true;
|
doLoad = true;
|
||||||
break;
|
break;
|
||||||
case Downshift.stateChangeTypes.changeInput:
|
case DownshiftChangeTypes.changeInput:
|
||||||
update.highlightedIndex = 0;
|
update.highlightedIndex = 0;
|
||||||
|
case DownshiftChangeTypes.keyDownArrowDown:
|
||||||
|
case DownshiftChangeTypes.keyDownArrowUp:
|
||||||
|
case DownshiftChangeTypes.itemMouseEnter:
|
||||||
|
update = {
|
||||||
|
...update,
|
||||||
|
...changes
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,30 +479,38 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
onChange('');
|
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>) {
|
renderValue({inputValue, isOpen}: ControllerStateAndHelpers<any>) {
|
||||||
const {
|
const {
|
||||||
multiple,
|
multiple,
|
||||||
placeholder,
|
placeholder,
|
||||||
classPrefix: ns,
|
classPrefix: ns,
|
||||||
labelField,
|
labelField,
|
||||||
searchable,
|
|
||||||
creatable,
|
|
||||||
disabled
|
disabled
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const selection = this.state.selection;
|
const selection = this.state.selection;
|
||||||
|
|
||||||
if (
|
|
||||||
searchable &&
|
|
||||||
!creatable &&
|
|
||||||
inputValue &&
|
|
||||||
(multiple ? !selection.length : true)
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!selection.length) {
|
if (!selection.length) {
|
||||||
return creatable && inputValue ? null : (
|
return (
|
||||||
<div key="placeholder" className={`${ns}Select-placeholder`}>
|
<div key="placeholder" className={`${ns}Select-placeholder`}>
|
||||||
{placeholder}
|
{placeholder}
|
||||||
</div>
|
</div>
|
||||||
|
@ -505,21 +530,24 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
{item[labelField || 'label']}
|
{item[labelField || 'label']}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : inputValue && isOpen ? null : (
|
) : (
|
||||||
<div className={`${ns}Select-value`} key={index}>
|
<div className={`${ns}Select-value`} key={index}>
|
||||||
{item.label}
|
{item[labelField || 'label']}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderOuter({
|
renderOuter(
|
||||||
selectedItem,
|
{
|
||||||
getItemProps,
|
selectedItem,
|
||||||
highlightedIndex,
|
getItemProps,
|
||||||
inputValue,
|
highlightedIndex,
|
||||||
isOpen
|
inputValue,
|
||||||
}: ControllerStateAndHelpers<any>) {
|
isOpen
|
||||||
|
}: ControllerStateAndHelpers<any>,
|
||||||
|
getInputProps: any
|
||||||
|
) {
|
||||||
const {
|
const {
|
||||||
popOverContainer,
|
popOverContainer,
|
||||||
options,
|
options,
|
||||||
|
@ -528,11 +556,16 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
noResultsText,
|
noResultsText,
|
||||||
loadOptions,
|
loadOptions,
|
||||||
creatable,
|
creatable,
|
||||||
promptTextCreator,
|
|
||||||
multiple,
|
multiple,
|
||||||
classnames: cx,
|
classnames: cx,
|
||||||
checkAll,
|
checkAll,
|
||||||
checkAllLabel
|
checkAllLabel,
|
||||||
|
searchable,
|
||||||
|
createBtnLabel,
|
||||||
|
disabled,
|
||||||
|
searchPromptText,
|
||||||
|
editable,
|
||||||
|
removable
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {selection} = this.state;
|
const {selection} = this.state;
|
||||||
|
|
||||||
|
@ -545,39 +578,41 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
})
|
})
|
||||||
: options.concat();
|
: options.concat();
|
||||||
|
|
||||||
if (multiple) {
|
if (multiple && checkAll) {
|
||||||
if (checkAll) {
|
const optionsValues = options.map(option => option.value);
|
||||||
const optionsValues = options.map(option => option.value);
|
const selectionValues = selection.map(select => select.value);
|
||||||
const selectionValues = selection.map(select => select.value);
|
checkedAll = optionsValues.every(
|
||||||
checkedAll = optionsValues.every(
|
option => selectionValues.indexOf(option) > -1
|
||||||
option => selectionValues.indexOf(option) > -1
|
);
|
||||||
);
|
checkedPartial = optionsValues.some(
|
||||||
checkedPartial = optionsValues.some(
|
option => selectionValues.indexOf(option) > -1
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<div ref={this.menu} className={cx('Select-menu')}>
|
<div ref={this.menu} className={cx('Select-menu')}>
|
||||||
{multiple && checkAll ? (
|
{searchable ? (
|
||||||
<div className={cx('Select-checkAll')}>
|
<div
|
||||||
|
className={cx(`Select-input`, {
|
||||||
|
'is-focused': this.state.isFocused
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<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
|
<Checkbox
|
||||||
checked={checkedPartial}
|
checked={checkedPartial}
|
||||||
partial={checkedPartial && !checkedAll}
|
partial={checkedPartial && !checkedAll}
|
||||||
|
@ -587,11 +622,12 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{filtedOptions.length ? (
|
{filtedOptions.length ? (
|
||||||
filtedOptions.map((item, index) => {
|
filtedOptions.map((item, index) => {
|
||||||
const checked = checkAll
|
const checked = checkAll
|
||||||
? selection.some((o: Option) => o.value == item.value)
|
? selection.some((o: Option) => o.value == item.value)
|
||||||
: false;
|
: !!~selectedItem.indexOf(item);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -609,15 +645,32 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
(Array.isArray(selectedItem) && ~selectedItem.indexOf(item))
|
(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
|
<Checkbox
|
||||||
checked={checked}
|
checked={checked}
|
||||||
trueValue={item.value}
|
trueValue={item.value}
|
||||||
onChange={() => this.handleChange(item)}
|
onChange={() => this.handleChange(item)}
|
||||||
>
|
>
|
||||||
{item.isNew
|
{item.disabled
|
||||||
? promptTextCreator(item.label as string)
|
|
||||||
: item.disabled
|
|
||||||
? item[labelField]
|
? item[labelField]
|
||||||
: highlight(
|
: highlight(
|
||||||
item[labelField],
|
item[labelField],
|
||||||
|
@ -625,8 +678,6 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
cx('Select-option-hl')
|
cx('Select-option-hl')
|
||||||
)}
|
)}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
) : item.isNew ? (
|
|
||||||
promptTextCreator(item.label as string)
|
|
||||||
) : (
|
) : (
|
||||||
<span>
|
<span>
|
||||||
{item.disabled
|
{item.disabled
|
||||||
|
@ -643,32 +694,56 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
<div className={cx('Select-option Select-option--placeholder')}>
|
<div className={cx('Select-noResult')}>{noResultsText}</div>
|
||||||
{noResultsText}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{creatable && !disabled ? (
|
||||||
|
<a className={cx('Select-addBtn')} onClick={this.handleAddClick}>
|
||||||
|
<Icon icon="plus" className="icon" />
|
||||||
|
{createBtnLabel}
|
||||||
|
</a>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (popOverContainer) {
|
return (
|
||||||
return (
|
<Overlay
|
||||||
<Overlay
|
container={popOverContainer || this.getTarget}
|
||||||
container={popOverContainer}
|
target={this.getTarget}
|
||||||
placement="left-bottom-left-top"
|
show
|
||||||
target={this.getTarget}
|
>
|
||||||
show
|
<PopOver
|
||||||
|
overlay
|
||||||
|
className={cx('Select-popover')}
|
||||||
|
style={{width: this.target ? this.target.offsetWidth : 'auto'}}
|
||||||
|
onHide={this.close}
|
||||||
>
|
>
|
||||||
<PopOver
|
{menu}
|
||||||
className={cx('Select-popover')}
|
</PopOver>
|
||||||
style={{width: this.target ? this.target.offsetWidth : 'auto'}}
|
</Overlay>
|
||||||
>
|
);
|
||||||
{menu}
|
|
||||||
</PopOver>
|
// if (popOverContainer) {
|
||||||
</Overlay>
|
// return (
|
||||||
);
|
// <Overlay
|
||||||
} else {
|
// container={popOverContainer}
|
||||||
return <div className={cx('Select-menuOuter')}>{menu}</div>;
|
// 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() {
|
render() {
|
||||||
|
@ -697,14 +772,14 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
inputValue={inputValue}
|
inputValue={inputValue}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
onStateChange={this.handleStateChange}
|
onStateChange={this.handleStateChange}
|
||||||
onOuterClick={this.close}
|
// onOuterClick={this.close}
|
||||||
itemToString={item => (item ? item[labelField] : '')}
|
itemToString={item => (item ? item[labelField] : '')}
|
||||||
>
|
>
|
||||||
{(options: ControllerStateAndHelpers<any>) => {
|
{(options: ControllerStateAndHelpers<any>) => {
|
||||||
const {isOpen, getInputProps} = options;
|
const {isOpen, getInputProps} = options;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
tabIndex={searchable || disabled ? -1 : 0}
|
tabIndex={disabled ? -1 : 0}
|
||||||
onKeyPress={this.handleKeyPress}
|
onKeyPress={this.handleKeyPress}
|
||||||
onClick={this.toggle}
|
onClick={this.toggle}
|
||||||
onFocus={this.onFocus}
|
onFocus={this.onFocus}
|
||||||
|
@ -724,22 +799,6 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
>
|
>
|
||||||
<div className={cx(`Select-valueWrap`)}>
|
<div className={cx(`Select-valueWrap`)}>
|
||||||
{this.renderValue(options)}
|
{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>
|
</div>
|
||||||
{clearable && !disabled && value && value.length ? (
|
{clearable && !disabled && value && value.length ? (
|
||||||
<a onClick={this.clearValue} className={cx('Select-clear')}>
|
<a onClick={this.clearValue} className={cx('Select-clear')}>
|
||||||
|
@ -753,7 +812,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<span className={cx('Select-arrow')} />
|
<span className={cx('Select-arrow')} />
|
||||||
{isOpen ? this.renderOuter(options) : null}
|
{isOpen ? this.renderOuter(options, getInputProps) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -5,11 +5,19 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {eachTree, isVisible, isObject, autobind} from '../utils/helper';
|
import {
|
||||||
|
eachTree,
|
||||||
|
isVisible,
|
||||||
|
autobind,
|
||||||
|
findTreeIndex,
|
||||||
|
hasAbility,
|
||||||
|
createObject
|
||||||
|
} from '../utils/helper';
|
||||||
import {Option, Options, value2array} from './Checkboxes';
|
import {Option, Options, value2array} from './Checkboxes';
|
||||||
import {ClassNamesFn, themeable} from '../theme';
|
import {ClassNamesFn, themeable} from '../theme';
|
||||||
import {highlight} from '../renderers/Form/Options';
|
import {highlight} from '../renderers/Form/Options';
|
||||||
import {Icon} from './icons';
|
import {Icon} from './icons';
|
||||||
|
import Checkbox from './Checkbox';
|
||||||
|
|
||||||
interface TreeSelectorProps {
|
interface TreeSelectorProps {
|
||||||
classPrefix: string;
|
classPrefix: string;
|
||||||
|
@ -32,18 +40,19 @@ interface TreeSelectorProps {
|
||||||
// 多选时,选中父节点时,是否只将起子节点加入到值中。
|
// 多选时,选中父节点时,是否只将起子节点加入到值中。
|
||||||
onlyChildren?: boolean;
|
onlyChildren?: boolean;
|
||||||
// 名称、取值等字段名映射
|
// 名称、取值等字段名映射
|
||||||
nameField?: string;
|
labelField: string;
|
||||||
valueField?: string;
|
valueField: string;
|
||||||
iconField?: string;
|
iconField: string;
|
||||||
unfoldedField?: string;
|
unfoldedField: string;
|
||||||
foldedField?: string;
|
foldedField: string;
|
||||||
disabledField?: string;
|
disabledField: string;
|
||||||
|
|
||||||
className?: string;
|
className?: string;
|
||||||
itemClassName?: string;
|
itemClassName?: string;
|
||||||
joinValues?: boolean;
|
joinValues?: boolean;
|
||||||
extractValue?: boolean;
|
extractValue?: boolean;
|
||||||
delimiter?: string;
|
delimiter?: string;
|
||||||
data: Options;
|
options: Options;
|
||||||
value: any;
|
value: any;
|
||||||
onChange: Function;
|
onChange: Function;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
@ -54,26 +63,31 @@ interface TreeSelectorProps {
|
||||||
selfDisabledAffectChildren?: boolean;
|
selfDisabledAffectChildren?: boolean;
|
||||||
minLength?: number;
|
minLength?: number;
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
addMode?: 'dialog' | 'normal';
|
|
||||||
addable?: boolean;
|
// 是否为内建 增、改、删。当有复杂表单的时候直接抛出去让外层能统一处理
|
||||||
onAdd?: Function;
|
bultinCUD?: boolean;
|
||||||
openAddDialog?: Function;
|
rootCreatable?: boolean;
|
||||||
editMode?: 'dialog' | 'normal';
|
creatable?: boolean;
|
||||||
onEdit?: Function;
|
onAdd?: (
|
||||||
|
idx?: number | Array<number>,
|
||||||
|
value?: any,
|
||||||
|
skipForm?: boolean
|
||||||
|
) => void;
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
openEditDialog?: Function;
|
onEdit?: (value: Option, origin?: Option, skipForm?: boolean) => void;
|
||||||
removable?: boolean;
|
removable?: boolean;
|
||||||
onRemove?: Function;
|
onDelete?: (value: Option) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TreeSelectorState {
|
interface TreeSelectorState {
|
||||||
value: Array<any>;
|
value: Array<any>;
|
||||||
unfolded: {[propName: string]: string};
|
unfolded: {[propName: string]: string};
|
||||||
editItem: Option | null;
|
|
||||||
addItem: Option | null;
|
inputValue: string;
|
||||||
addingItem: Option | null;
|
addingParent: Option | null;
|
||||||
|
isAdding: boolean;
|
||||||
|
isEditing: boolean;
|
||||||
editingItem: Option | null;
|
editingItem: Option | null;
|
||||||
addTop: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TreeSelector extends React.Component<
|
export class TreeSelector extends React.Component<
|
||||||
|
@ -89,7 +103,7 @@ export class TreeSelector extends React.Component<
|
||||||
disabled: false,
|
disabled: false,
|
||||||
withChildren: false,
|
withChildren: false,
|
||||||
onlyChildren: false,
|
onlyChildren: false,
|
||||||
nameField: 'name',
|
labelField: 'label',
|
||||||
valueField: 'value',
|
valueField: 'value',
|
||||||
iconField: 'icon',
|
iconField: 'icon',
|
||||||
unfoldedField: 'unfolded',
|
unfoldedField: 'unfolded',
|
||||||
|
@ -115,14 +129,15 @@ export class TreeSelector extends React.Component<
|
||||||
multiple: props.multiple,
|
multiple: props.multiple,
|
||||||
delimiter: props.delimiter,
|
delimiter: props.delimiter,
|
||||||
valueField: props.valueField,
|
valueField: props.valueField,
|
||||||
options: props.data
|
options: props.options
|
||||||
}),
|
}),
|
||||||
unfolded: this.syncUnFolded(props),
|
unfolded: this.syncUnFolded(props),
|
||||||
editItem: null, // 点击编辑时的 item
|
|
||||||
addItem: null, // 点击添加时的 item
|
inputValue: '',
|
||||||
addingItem: null, // 添加后的 item
|
addingParent: null,
|
||||||
editingItem: null, // 编辑后的 item
|
isAdding: false,
|
||||||
addTop: false // 添加一级
|
isEditing: false,
|
||||||
|
editingItem: null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +146,7 @@ export class TreeSelector extends React.Component<
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.props.value !== nextProps.value ||
|
this.props.value !== nextProps.value ||
|
||||||
this.props.data !== nextProps.data
|
this.props.options !== nextProps.options
|
||||||
) {
|
) {
|
||||||
toUpdate.value = value2array(nextProps.value, {
|
toUpdate.value = value2array(nextProps.value, {
|
||||||
joinValues: nextProps.joinValues,
|
joinValues: nextProps.joinValues,
|
||||||
|
@ -139,11 +154,11 @@ export class TreeSelector extends React.Component<
|
||||||
multiple: nextProps.multiple,
|
multiple: nextProps.multiple,
|
||||||
delimiter: nextProps.delimiter,
|
delimiter: nextProps.delimiter,
|
||||||
valueField: nextProps.valueField,
|
valueField: nextProps.valueField,
|
||||||
options: nextProps.data
|
options: nextProps.options
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.data !== nextProps.data) {
|
if (this.props.options !== nextProps.options) {
|
||||||
toUpdate.unfolded = this.syncUnFolded(nextProps);
|
toUpdate.unfolded = this.syncUnFolded(nextProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +170,7 @@ export class TreeSelector extends React.Component<
|
||||||
let unfolded: {[propName: string]: string} = {};
|
let unfolded: {[propName: string]: string} = {};
|
||||||
const {foldedField, unfoldedField} = this.props;
|
const {foldedField, unfoldedField} = this.props;
|
||||||
|
|
||||||
eachTree(props.data, (node: Option, index, level) => {
|
eachTree(props.options, (node: Option, index, level) => {
|
||||||
if (node.children && node.children.length) {
|
if (node.children && node.children.length) {
|
||||||
let ret: any = true;
|
let ret: any = true;
|
||||||
|
|
||||||
|
@ -179,7 +194,6 @@ export class TreeSelector extends React.Component<
|
||||||
@autobind
|
@autobind
|
||||||
toggleUnfolded(node: any) {
|
toggleUnfolded(node: any) {
|
||||||
this.setState({
|
this.setState({
|
||||||
addItem: null,
|
|
||||||
unfolded: {
|
unfolded: {
|
||||||
...this.state.unfolded,
|
...this.state.unfolded,
|
||||||
[node[this.props.valueField as string]]: !this.state.unfolded[
|
[node[this.props.valueField as string]]: !this.state.unfolded[
|
||||||
|
@ -308,128 +322,124 @@ export class TreeSelector extends React.Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleAdd(item: Option | null, isFolder: boolean) {
|
handleAdd(parent: Option | null = null) {
|
||||||
const {addMode, openAddDialog, valueField} = this.props;
|
const {bultinCUD, onAdd, options} = this.props;
|
||||||
let {unfolded} = this.state;
|
let idx: Array<number> | undefined = undefined;
|
||||||
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({
|
if (!bultinCUD) {
|
||||||
addItem: item,
|
idx = parent
|
||||||
editItem: null,
|
? findTreeIndex(options, item => item === parent)
|
||||||
unfolded
|
: undefined;
|
||||||
});
|
return onAdd && onAdd(idx);
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
addTop: true,
|
isEditing: false,
|
||||||
editItem: null,
|
isAdding: true,
|
||||||
addItem: null
|
addingParent: parent
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleEdit(item: Option) {
|
handleEdit(item: Option) {
|
||||||
const {editMode, openEditDialog} = this.props;
|
const labelField = this.props.labelField;
|
||||||
const {addItem} = this.state;
|
this.setState({
|
||||||
if (editMode === 'dialog') {
|
isEditing: true,
|
||||||
openEditDialog && openEditDialog(item);
|
isAdding: false,
|
||||||
addItem &&
|
editingItem: item,
|
||||||
this.setState({
|
inputValue: item[labelField]
|
||||||
addItem: null
|
});
|
||||||
});
|
|
||||||
} else if (editMode === 'normal') {
|
|
||||||
this.setState({
|
|
||||||
editItem: item,
|
|
||||||
addItem: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleRemove(item: Option) {
|
handleRemove(item: Option) {
|
||||||
const {onRemove} = this.props;
|
const {onDelete} = this.props;
|
||||||
onRemove && onRemove(item);
|
|
||||||
|
onDelete && onDelete(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleConfirmOnAdd() {
|
handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||||
const {onAdd} = this.props;
|
|
||||||
const {addItem: parent, addingItem} = this.state;
|
|
||||||
onAdd &&
|
|
||||||
onAdd({
|
|
||||||
...addingItem,
|
|
||||||
parent: parent
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
addingItem: null,
|
inputValue: e.currentTarget.value
|
||||||
addItem: null,
|
|
||||||
addTop: false
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleCancelOnAdd() {
|
handleConfirm() {
|
||||||
this.setState({
|
const {
|
||||||
addItem: null,
|
inputValue: value,
|
||||||
addTop: false
|
isAdding,
|
||||||
});
|
addingParent,
|
||||||
}
|
editingItem,
|
||||||
|
isEditing
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
@autobind
|
if (!value) {
|
||||||
handleConfirmOnEdit() {
|
return;
|
||||||
const {onEdit} = this.props;
|
}
|
||||||
let {editingItem, editItem: prevItem} = this.state;
|
|
||||||
onEdit &&
|
|
||||||
onEdit({
|
|
||||||
...editingItem,
|
|
||||||
prev: prevItem
|
|
||||||
});
|
|
||||||
this.setState({
|
|
||||||
editingItem: null,
|
|
||||||
editItem: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
const {labelField, onAdd, options, onEdit} = this.props;
|
||||||
handleCancelOnEdit() {
|
this.setState(
|
||||||
this.setState({
|
{
|
||||||
editItem: null
|
inputValue: '',
|
||||||
});
|
isAdding: false,
|
||||||
}
|
isEditing: false
|
||||||
|
},
|
||||||
@autobind
|
() => {
|
||||||
handleChangeOnAdd(value: string) {
|
if (isAdding && onAdd) {
|
||||||
this.setState({
|
let idx =
|
||||||
addingItem: {
|
(addingParent &&
|
||||||
label: value
|
findTreeIndex(options, item => item === addingParent)) ||
|
||||||
|
[];
|
||||||
|
onAdd(idx.concat(0), {[labelField]: value}, true);
|
||||||
|
} else if (isEditing && onEdit) {
|
||||||
|
onEdit(
|
||||||
|
{
|
||||||
|
...editingItem,
|
||||||
|
[labelField]: value
|
||||||
|
},
|
||||||
|
editingItem!,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleChangeOnEdit(item: Option, value: string) {
|
handleCancel() {
|
||||||
let {editItem} = this.state;
|
|
||||||
this.setState({
|
this.setState({
|
||||||
editingItem: {
|
inputValue: '',
|
||||||
...item,
|
isAdding: false,
|
||||||
label: value || (editItem as Option)['label']
|
isEditing: false
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderInput(prfix: JSX.Element | null = null) {
|
||||||
|
const {classnames: cx} = this.props;
|
||||||
|
const {inputValue} = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cx('Tree-itemLabel')}>
|
||||||
|
<div className={cx('Tree-itemInput')}>
|
||||||
|
{prfix}
|
||||||
|
<input
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
value={inputValue}
|
||||||
|
placeholder="请输入"
|
||||||
|
/>
|
||||||
|
<a data-tooltip="取消" onClick={this.handleCancel}>
|
||||||
|
<Icon icon="close" className="icon" />
|
||||||
|
</a>
|
||||||
|
<a data-tooltip="确认" onClick={this.handleConfirm}>
|
||||||
|
<Icon icon="check" className="icon" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
renderList(
|
renderList(
|
||||||
list: Options,
|
list: Options,
|
||||||
|
@ -442,27 +452,34 @@ export class TreeSelector extends React.Component<
|
||||||
showRadio,
|
showRadio,
|
||||||
multiple,
|
multiple,
|
||||||
disabled,
|
disabled,
|
||||||
nameField = '',
|
labelField,
|
||||||
valueField = '',
|
valueField,
|
||||||
iconField = '',
|
iconField,
|
||||||
disabledField = '',
|
disabledField,
|
||||||
cascade,
|
cascade,
|
||||||
selfDisabledAffectChildren,
|
selfDisabledAffectChildren,
|
||||||
onlyChildren,
|
onlyChildren,
|
||||||
classnames: cx,
|
classnames: cx,
|
||||||
highlightTxt,
|
highlightTxt,
|
||||||
data,
|
options,
|
||||||
maxLength,
|
maxLength,
|
||||||
minLength,
|
minLength,
|
||||||
addable,
|
creatable,
|
||||||
editable,
|
editable,
|
||||||
removable
|
removable
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {addItem, editItem, unfolded, addTop, value: stateValue} = this.state;
|
const {
|
||||||
|
unfolded,
|
||||||
|
value: stateValue,
|
||||||
|
isAdding,
|
||||||
|
addingParent,
|
||||||
|
editingItem,
|
||||||
|
isEditing
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
let childrenChecked = 0;
|
let childrenChecked = 0;
|
||||||
let ret = list.map((item, key) => {
|
let ret = list.map((item, key) => {
|
||||||
if (!isVisible(item as any, data)) {
|
if (!isVisible(item as any, options)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,25 +525,19 @@ export class TreeSelector extends React.Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkbox: JSX.Element | null = multiple ? (
|
const checkbox: JSX.Element | null = multiple ? (
|
||||||
<label className={cx(`Checkbox Checkbox--checkbox Checkbox--sm`)}>
|
<Checkbox
|
||||||
<input
|
size="sm"
|
||||||
type="checkbox"
|
disabled={nodeDisabled}
|
||||||
disabled={nodeDisabled}
|
checked={checked}
|
||||||
checked={selfChecked}
|
onChange={this.handleCheck.bind(this, item)}
|
||||||
onChange={e => this.handleCheck(item, e.currentTarget.checked)}
|
/>
|
||||||
/>
|
|
||||||
<i />
|
|
||||||
</label>
|
|
||||||
) : showRadio ? (
|
) : showRadio ? (
|
||||||
<label className={cx(`Checkbox Checkbox--radio Checkbox--sm`)}>
|
<Checkbox
|
||||||
<input
|
size="sm"
|
||||||
type="radio"
|
disabled={nodeDisabled}
|
||||||
disabled={nodeDisabled}
|
checked={checked}
|
||||||
checked={checked}
|
onChange={this.handleSelect.bind(this, item)}
|
||||||
onChange={() => this.handleSelect(item)}
|
/>
|
||||||
/>
|
|
||||||
<i />
|
|
||||||
</label>
|
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
const isLeaf = !item.children || !item.children.length;
|
const isLeaf = !item.children || !item.children.length;
|
||||||
|
@ -538,10 +549,17 @@ export class TreeSelector extends React.Component<
|
||||||
'Tree-item--isLeaf': isLeaf
|
'Tree-item--isLeaf': isLeaf
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{!editItem ||
|
{isEditing && editingItem === item ? (
|
||||||
(isObject(editItem) &&
|
this.renderInput(checkbox)
|
||||||
(editItem as Option)[valueField] !== item[valueField]) ? (
|
) : (
|
||||||
<a>
|
<div
|
||||||
|
className={cx('Tree-itemLabel', {
|
||||||
|
'is-children-checked':
|
||||||
|
multiple && !cascade && tmpChildrenChecked && !nodeDisabled,
|
||||||
|
'is-checked': checked,
|
||||||
|
'is-disabled': nodeDisabled
|
||||||
|
})}
|
||||||
|
>
|
||||||
{!isLeaf ? (
|
{!isLeaf ? (
|
||||||
<i
|
<i
|
||||||
onClick={() => this.toggleUnfolded(item)}
|
onClick={() => this.toggleUnfolded(item)}
|
||||||
|
@ -549,26 +567,14 @@ export class TreeSelector extends React.Component<
|
||||||
'is-folded': !unfolded[item[valueField]]
|
'is-folded': !unfolded[item[valueField]]
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : (
|
||||||
|
<span className={cx('Tree-itemArrowPlaceholder')} />
|
||||||
{showIcon ? (
|
)}
|
||||||
<i
|
|
||||||
className={cx(
|
|
||||||
`Tree-itemIcon ${item[iconField] ||
|
|
||||||
(childrenItems ? 'Tree-folderIcon' : 'Tree-leafIcon')}`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{checkbox}
|
{checkbox}
|
||||||
|
|
||||||
<span
|
<span
|
||||||
className={cx('Tree-itemText', {
|
className={cx('Tree-itemText')}
|
||||||
'is-children-checked':
|
|
||||||
multiple && !cascade && tmpChildrenChecked && !nodeDisabled,
|
|
||||||
'is-checked': checked,
|
|
||||||
'is-disabled': nodeDisabled
|
|
||||||
})}
|
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
!nodeDisabled &&
|
!nodeDisabled &&
|
||||||
(multiple
|
(multiple
|
||||||
|
@ -576,81 +582,72 @@ export class TreeSelector extends React.Component<
|
||||||
: this.handleSelect(item))
|
: this.handleSelect(item))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{showIcon ? (
|
||||||
|
<i
|
||||||
|
className={cx(
|
||||||
|
`Tree-itemIcon ${item[iconField] ||
|
||||||
|
(childrenItems ? 'Tree-folderIcon' : 'Tree-leafIcon')}`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{highlightTxt
|
{highlightTxt
|
||||||
? highlight(item[nameField], highlightTxt)
|
? highlight(item[labelField], highlightTxt)
|
||||||
: item[nameField]}
|
: item[labelField]}
|
||||||
</span>
|
</span>
|
||||||
{!nodeDisabled && !addTop && !addItem && !editItem ? (
|
|
||||||
<span className={cx('Tree-item-icons')}>
|
{!nodeDisabled && !isAdding && !isEditing ? (
|
||||||
{addable ? (
|
<div className={cx('Tree-item-icons')}>
|
||||||
<Icon
|
{creatable && hasAbility(item, 'creatable') ? (
|
||||||
icon="plus"
|
<a
|
||||||
className="icon"
|
onClick={this.handleAdd.bind(this, item)}
|
||||||
onClick={() => this.handleAdd(item, !isLeaf)}
|
data-tooltip="添加孩子节点"
|
||||||
/>
|
>
|
||||||
|
<Icon icon="plus" className="icon" />
|
||||||
|
</a>
|
||||||
) : null}
|
) : null}
|
||||||
{removable ? (
|
|
||||||
<Icon
|
{removable && hasAbility(item, 'removable') ? (
|
||||||
icon="minus"
|
<a
|
||||||
className="icon"
|
onClick={this.handleRemove.bind(this, item)}
|
||||||
onClick={() => this.handleRemove(item)}
|
data-tooltip="移除该节点"
|
||||||
/>
|
>
|
||||||
|
<Icon icon="minus" className="icon" />
|
||||||
|
</a>
|
||||||
) : null}
|
) : null}
|
||||||
{editable ? (
|
|
||||||
<Icon
|
{editable && hasAbility(item, 'editable') ? (
|
||||||
icon="pencil"
|
<a
|
||||||
className="icon"
|
onClick={this.handleEdit.bind(this, item)}
|
||||||
onClick={() => this.handleEdit(item)}
|
data-tooltip="编辑该节点"
|
||||||
/>
|
>
|
||||||
|
<Icon icon="pencil" className="icon" />
|
||||||
|
</a>
|
||||||
) : null}
|
) : null}
|
||||||
</span>
|
</div>
|
||||||
) : null}
|
) : 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>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* 有children而且为展开状态 或者 添加child时 */}
|
{/* 有children而且为展开状态 或者 添加child时 */}
|
||||||
{(childrenItems && unfolded[item[valueField]]) ||
|
{(childrenItems && unfolded[item[valueField]]) ||
|
||||||
(addItem && addItem[valueField] === item[valueField]) ? (
|
(isAdding && addingParent === item) ? (
|
||||||
<ul className={cx('Tree-sublist')}>
|
<ul className={cx('Tree-sublist')}>
|
||||||
{addItem && addItem[valueField] === item[valueField] ? (
|
{isAdding && addingParent === item ? (
|
||||||
<li>
|
<li className={cx('Tree-item')}>
|
||||||
<input
|
{this.renderInput(
|
||||||
onChange={e =>
|
checkbox
|
||||||
this.handleChangeOnAdd(e.currentTarget.value)
|
? React.cloneElement(checkbox, {
|
||||||
}
|
checked: false,
|
||||||
/>
|
disabled: true
|
||||||
<Icon
|
})
|
||||||
icon="check"
|
: null
|
||||||
className="icon"
|
)}
|
||||||
onClick={this.handleConfirmOnAdd}
|
|
||||||
/>
|
|
||||||
<Icon
|
|
||||||
icon="close"
|
|
||||||
className="icon"
|
|
||||||
onClick={this.handleCancelOnAdd}
|
|
||||||
/>
|
|
||||||
</li>
|
</li>
|
||||||
) : null}
|
) : null}
|
||||||
{childrenItems}
|
{childrenItems}
|
||||||
</ul>
|
</ul>
|
||||||
|
) : !childrenItems && item.placeholder ? (
|
||||||
|
<div className={cx('Tree-placeholder')}>{item.placeholder}</div>
|
||||||
) : null}
|
) : null}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
@ -670,68 +667,76 @@ export class TreeSelector extends React.Component<
|
||||||
rootLabel,
|
rootLabel,
|
||||||
showIcon,
|
showIcon,
|
||||||
classnames: cx,
|
classnames: cx,
|
||||||
addable
|
creatable,
|
||||||
|
rootCreatable,
|
||||||
|
disabled
|
||||||
} = this.props;
|
} = this.props;
|
||||||
let data = this.props.data;
|
let options = this.props.options;
|
||||||
const {value, addTop} = this.state;
|
const {value, isAdding, addingParent, isEditing, inputValue} = this.state;
|
||||||
|
|
||||||
|
let addBtn = null;
|
||||||
|
|
||||||
|
if (creatable && rootCreatable !== false && hideRoot) {
|
||||||
|
addBtn = (
|
||||||
|
<a
|
||||||
|
className={cx('Tree-addTopBtn', {
|
||||||
|
'is-disabled': isAdding || isEditing
|
||||||
|
})}
|
||||||
|
onClick={this.handleAdd.bind(this, null)}
|
||||||
|
>
|
||||||
|
<Icon icon="plus" className="icon" />
|
||||||
|
<span>添加一级节点</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(`Tree ${className || ''}`)}>
|
<div className={cx(`Tree ${className || ''}`)}>
|
||||||
{data && data.length ? (
|
{options && options.length ? (
|
||||||
<ul className={cx('Tree-list')}>
|
<ul className={cx('Tree-list')}>
|
||||||
{hideRoot ? (
|
{hideRoot ? (
|
||||||
this.renderList(data, value, false).dom
|
<>
|
||||||
) : (
|
{addBtn}
|
||||||
<li className={cx('Tree-item Tree-rootItem')}>
|
{isAdding && !addingParent ? (
|
||||||
<a>
|
<li className={cx('Tree-item')}>{this.renderInput()}</li>
|
||||||
{showIcon ? (
|
|
||||||
<i className={cx('Tree-itemIcon Tree-rootIcon')} />
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<label
|
|
||||||
className={cx('Tree-itemLabel', {
|
|
||||||
'is-checked': !value || !value.length
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={cx('Tree-itemText')}
|
|
||||||
onClick={this.clearSelect}
|
|
||||||
>
|
|
||||||
{rootLabel}
|
|
||||||
</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}
|
) : null}
|
||||||
|
{this.renderList(options, value, false).dom}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<li
|
||||||
|
className={cx('Tree-rootItem', {
|
||||||
|
'is-checked': !value || !value.length
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className={cx('Tree-itemLabel')}>
|
||||||
|
<span className={cx('Tree-itemText')}>
|
||||||
|
{showIcon ? (
|
||||||
|
<i className={cx('Tree-itemIcon Tree-rootIcon')} />
|
||||||
|
) : null}
|
||||||
|
{rootLabel}
|
||||||
|
</span>
|
||||||
|
{!disabled &&
|
||||||
|
creatable &&
|
||||||
|
rootCreatable !== false &&
|
||||||
|
!isAdding &&
|
||||||
|
!isEditing ? (
|
||||||
|
<div className={cx('Tree-item-icons')}>
|
||||||
|
{creatable ? (
|
||||||
|
<a
|
||||||
|
onClick={this.handleAdd.bind(this, null)}
|
||||||
|
data-tooltip="添加一级节点"
|
||||||
|
>
|
||||||
|
<Icon icon="plus" className="icon" />
|
||||||
|
</a>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
<ul className={cx('Tree-sublist')}>
|
<ul className={cx('Tree-sublist')}>
|
||||||
{this.renderList(data, value, false).dom}
|
{isAdding && !addingParent ? (
|
||||||
|
<li className={cx('Tree-item')}>{this.renderInput()}</li>
|
||||||
|
) : null}
|
||||||
|
{this.renderList(options, value, false).dom}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -48,6 +48,9 @@ import SuccessIcon from '../icons/success.svg';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import FailIcon from '../icons/fail.svg';
|
import FailIcon from '../icons/fail.svg';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import SearchIcon from '../icons/search.svg';
|
||||||
|
|
||||||
// 兼容原来的用法,后续不直接试用。
|
// 兼容原来的用法,后续不直接试用。
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export const closeIcon = <CloseIcon />;
|
export const closeIcon = <CloseIcon />;
|
||||||
|
@ -103,6 +106,7 @@ registerIcon('upload', UploadIcon);
|
||||||
registerIcon('file', FileIcon);
|
registerIcon('file', FileIcon);
|
||||||
registerIcon('success', SuccessIcon);
|
registerIcon('success', SuccessIcon);
|
||||||
registerIcon('fail', FailIcon);
|
registerIcon('fail', FailIcon);
|
||||||
|
registerIcon('search', SearchIcon);
|
||||||
|
|
||||||
export function Icon({
|
export function Icon({
|
||||||
icon,
|
icon,
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
<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">
|
<svg viewBox="0 0 13 9" version="1.1"
|
||||||
<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>
|
xmlns="http://www.w3.org/2000/svg" p-id="3506"
|
||||||
</svg>
|
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g transform="translate(6.656854, 2.656854) scale(-1, 1) rotate(-315.000000) translate(-6.656854, -2.656854) ">
|
||||||
|
<polygon id="path-1" points="11.1568542 5.15685425 11.1568542 -0.843145751 12.1568542 -0.843145751 12.1568542 6.15685425 1.15685425 6.15685425 1.15685425 5.15685425"></polygon>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 759 B After Width: | Height: | Size: 458 B |
|
@ -1,4 +1,4 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1024 1024" version="1.1" p-id="1463">
|
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 12 12" version="1.1">
|
||||||
<path d="M967.81435 106.836237 917.16274 56.18565 512 461.34839 106.836237 56.18565 56.184627 106.836237 461.34839 512 56.184627 917.163763 106.836237 967.815373 512 562.65161 917.16274 967.815373 967.81435 917.163763 562.650587 512Z" p-id="1464" data-spm-anchor-id="a313x.7781069.0.i0" />
|
<polygon id="path-1" points="6.0003653 5.2970518 10.5993691 0.6980479600000002 11.3064759 1.4051547400000004 6.7074721 6.0041586 11.3009516 10.5976381 10.5938448 11.3047449 6.0003653 6.7112654 1.4056713299999997 11.3059593 0.6985645500000004 10.5988525 5.2932585 6.0041586 0.6956119200000002 1.4065120000000002 1.4027187000000003 0.69940522"></polygon>
|
||||||
</svg>
|
</svg>
|
Before Width: | Height: | Size: 439 B After Width: | Height: | Size: 486 B |
|
@ -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;
|
control: any;
|
||||||
lastQuery: any;
|
lastQuery: any;
|
||||||
dataInvalid: boolean = false;
|
dataInvalid: boolean = false;
|
||||||
timer: number;
|
timer: NodeJS.Timeout;
|
||||||
mounted: boolean;
|
mounted: boolean;
|
||||||
constructor(props: CRUDProps) {
|
constructor(props: CRUDProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -331,7 +331,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
||||||
env.jumpTo(filter(action.redirect, data), action);
|
env.jumpTo(filter(action.redirect, data), action);
|
||||||
|
|
||||||
return store
|
return store
|
||||||
.saveRemote(action.api, data, {
|
.saveRemote(action.api!, data, {
|
||||||
successMessage:
|
successMessage:
|
||||||
(action.messages && action.messages.success) ||
|
(action.messages && action.messages.success) ||
|
||||||
(messages && messages.saveSuccess),
|
(messages && messages.saveSuccess),
|
||||||
|
|
|
@ -118,16 +118,7 @@ export default class DropDownButton extends React.Component<
|
||||||
|
|
||||||
if (popOverContainer) {
|
if (popOverContainer) {
|
||||||
return (
|
return (
|
||||||
<Overlay
|
<Overlay container={popOverContainer} target={() => this.target} show>
|
||||||
container={popOverContainer}
|
|
||||||
placement={
|
|
||||||
align === 'right'
|
|
||||||
? 'right-bottom-right-top'
|
|
||||||
: 'left-bottom-left-top'
|
|
||||||
}
|
|
||||||
target={() => this.target}
|
|
||||||
show
|
|
||||||
>
|
|
||||||
<PopOver
|
<PopOver
|
||||||
overlay
|
overlay
|
||||||
onHide={this.close}
|
onHide={this.close}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import {IFormStore, IFormItemStore} from '../../store/form';
|
import {IFormStore, IFormItemStore} from '../../store/form';
|
||||||
import debouce = require('lodash/debounce');
|
import debouce = require('lodash/debounce');
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,10 @@ import {
|
||||||
RendererConfig,
|
RendererConfig,
|
||||||
HocStoreFactory
|
HocStoreFactory
|
||||||
} from '../../factory';
|
} from '../../factory';
|
||||||
import {anyChanged, ucFirst, getWidthRate} from '../../utils/helper';
|
import {anyChanged, ucFirst, getWidthRate, autobind} from '../../utils/helper';
|
||||||
import {observer} from 'mobx-react';
|
import {observer} from 'mobx-react';
|
||||||
import {FormHorizontal, FormSchema} from '.';
|
import {FormHorizontal, FormSchema} from '.';
|
||||||
|
import {Schema} from '../../types';
|
||||||
|
|
||||||
export interface FormItemBasicConfig extends Partial<RendererConfig> {
|
export interface FormItemBasicConfig extends Partial<RendererConfig> {
|
||||||
type?: string;
|
type?: string;
|
||||||
|
@ -33,13 +34,11 @@ export interface FormItemBasicConfig extends Partial<RendererConfig> {
|
||||||
validate?: (values: any, value: any) => string | boolean;
|
validate?: (values: any, value: any) => string | boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FormItemState {
|
// 自己接收到属性。
|
||||||
isFocused: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FormItemProps extends RendererProps {
|
export interface FormItemProps extends RendererProps {
|
||||||
name?: string;
|
name?: string;
|
||||||
formStore?: IFormStore;
|
formStore?: IFormStore;
|
||||||
|
formItem?: IFormItemStore;
|
||||||
formInited: boolean;
|
formInited: boolean;
|
||||||
formMode: 'normal' | 'horizontal' | 'inline' | 'row' | 'default';
|
formMode: 'normal' | 'horizontal' | 'inline' | 'row' | 'default';
|
||||||
formHorizontal: FormHorizontal;
|
formHorizontal: FormHorizontal;
|
||||||
|
@ -56,7 +55,7 @@ export interface FormItemProps extends RendererProps {
|
||||||
values: {[propName: string]: any},
|
values: {[propName: string]: any},
|
||||||
submitOnChange?: boolean
|
submitOnChange?: boolean
|
||||||
) => void;
|
) => void;
|
||||||
addHook: (fn: Function, mode?: 'validate' | 'init') => void;
|
addHook: (fn: Function, mode?: 'validate' | 'init') => () => void;
|
||||||
removeHook: (fn: Function, mode?: 'validate' | 'init') => void;
|
removeHook: (fn: Function, mode?: 'validate' | 'init') => void;
|
||||||
renderFormItems: (
|
renderFormItems: (
|
||||||
schema: FormSchema,
|
schema: FormSchema,
|
||||||
|
@ -89,8 +88,10 @@ export interface FormItemProps extends RendererProps {
|
||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FormControlProps = RendererProps &
|
// 下发下去的属性
|
||||||
Exclude<
|
export type FormControlProps = RendererProps & {
|
||||||
|
onOpenDialog: (schema: Schema, data: any) => Promise<any>;
|
||||||
|
} & Exclude<
|
||||||
FormItemProps,
|
FormItemProps,
|
||||||
| 'inputClassName'
|
| 'inputClassName'
|
||||||
| 'renderControl'
|
| 'renderControl'
|
||||||
|
@ -114,29 +115,15 @@ export interface FormItemConfig extends FormItemBasicConfig {
|
||||||
component: FormControlComponent;
|
component: FormControlComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FormItemWrap extends React.Component<
|
export class FormItemWrap extends React.Component<FormItemProps> {
|
||||||
FormItemProps,
|
|
||||||
FormItemState
|
|
||||||
> {
|
|
||||||
reaction: any;
|
reaction: any;
|
||||||
|
|
||||||
constructor(props: FormItemProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isFocused: false
|
|
||||||
};
|
|
||||||
|
|
||||||
this.handleFocus = this.handleFocus.bind(this);
|
|
||||||
this.handleBlur = this.handleBlur.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
const {formItem: model} = this.props;
|
const {formItem: model} = this.props;
|
||||||
|
|
||||||
if (model) {
|
if (model) {
|
||||||
this.reaction = reaction(
|
this.reaction = reaction(
|
||||||
() => model.errors.join(''),
|
() => `${model.errors.join('')}${model.isFocused}${model.dialogOpen}`,
|
||||||
() => this.forceUpdate()
|
() => this.forceUpdate()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -146,20 +133,51 @@ export class FormItemWrap extends React.Component<
|
||||||
this.reaction && this.reaction();
|
this.reaction && this.reaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
handleFocus(e: any) {
|
handleFocus(e: any) {
|
||||||
this.setState({
|
const {formItem: model} = this.props;
|
||||||
isFocused: true
|
model && model.focus();
|
||||||
});
|
|
||||||
this.props.onFocus && this.props.onFocus(e);
|
this.props.onFocus && this.props.onFocus(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
handleBlur(e: any) {
|
handleBlur(e: any) {
|
||||||
this.setState({
|
const {formItem: model} = this.props;
|
||||||
isFocused: false
|
model && model.blur();
|
||||||
});
|
|
||||||
this.props.onBlur && this.props.onBlur(e);
|
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() {
|
renderControl() {
|
||||||
const {
|
const {
|
||||||
inputClassName,
|
inputClassName,
|
||||||
|
@ -179,6 +197,7 @@ export class FormItemWrap extends React.Component<
|
||||||
const controlSize = size || defaultSize;
|
const controlSize = size || defaultSize;
|
||||||
return renderControl({
|
return renderControl({
|
||||||
...rest,
|
...rest,
|
||||||
|
onOpenDialog: this.handleOpenDialog,
|
||||||
type,
|
type,
|
||||||
classnames: cx,
|
classnames: cx,
|
||||||
formItem: model,
|
formItem: model,
|
||||||
|
@ -299,7 +318,7 @@ export class FormItemWrap extends React.Component<
|
||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
|
|
||||||
{hint && this.state.isFocused
|
{hint && model && model.isFocused
|
||||||
? render('hint', hint, {
|
? render('hint', hint, {
|
||||||
className: cx(`Form-hint`)
|
className: cx(`Form-hint`)
|
||||||
})
|
})
|
||||||
|
@ -395,7 +414,7 @@ export class FormItemWrap extends React.Component<
|
||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
|
|
||||||
{hint && this.state.isFocused
|
{hint && model && model.isFocused
|
||||||
? render('hint', hint, {
|
? render('hint', hint, {
|
||||||
className: cx(`Form-hint`)
|
className: cx(`Form-hint`)
|
||||||
})
|
})
|
||||||
|
@ -490,7 +509,7 @@ export class FormItemWrap extends React.Component<
|
||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
|
|
||||||
{hint && this.state.isFocused
|
{hint && model && model.isFocused
|
||||||
? render('hint', hint, {
|
? render('hint', hint, {
|
||||||
className: cx(`Form-hint`)
|
className: cx(`Form-hint`)
|
||||||
})
|
})
|
||||||
|
@ -588,7 +607,7 @@ export class FormItemWrap extends React.Component<
|
||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{hint && this.state.isFocused
|
{hint && model && model.isFocused
|
||||||
? render('hint', hint, {
|
? render('hint', hint, {
|
||||||
className: cx(`Form-hint`)
|
className: cx(`Form-hint`)
|
||||||
})
|
})
|
||||||
|
@ -612,19 +631,38 @@ export class FormItemWrap extends React.Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {formMode, inputOnly, wrap} = this.props;
|
const {formMode, inputOnly, wrap, render, formItem: model} = this.props;
|
||||||
|
|
||||||
if (wrap === false || inputOnly) {
|
if (wrap === false || inputOnly) {
|
||||||
return this.renderControl();
|
return this.renderControl();
|
||||||
}
|
}
|
||||||
|
|
||||||
return formMode === 'inline'
|
return (
|
||||||
? this.renderInline()
|
<>
|
||||||
: formMode === 'horizontal'
|
{formMode === 'inline'
|
||||||
? this.renderHorizontal()
|
? this.renderInline()
|
||||||
: formMode === 'row'
|
: formMode === 'horizontal'
|
||||||
? this.renderRow()
|
? this.renderHorizontal()
|
||||||
: this.renderNormal();
|
: 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 (
|
return (
|
||||||
<Control
|
<Control
|
||||||
{...rest}
|
{...rest}
|
||||||
|
onOpenDialog={this.handleOpenDialog}
|
||||||
size={config.sizeMutable !== false ? undefined : size}
|
size={config.sizeMutable !== false ? undefined : size}
|
||||||
onFocus={this.handleFocus}
|
onFocus={this.handleFocus}
|
||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import cx from 'classnames';
|
||||||
import {FormControlProps, FormItem} from './Item';
|
import {FormControlProps, FormItem} from './Item';
|
||||||
import {buildApi, isValidApi, isEffectiveApi} from '../../utils/api';
|
import {buildApi, isValidApi, isEffectiveApi} from '../../utils/api';
|
||||||
import {Checkbox, Spinner} from '../../components';
|
import {Checkbox, Spinner} from '../../components';
|
||||||
|
import {autobind, setVariable} from '../../utils/helper';
|
||||||
|
|
||||||
export interface Column {
|
export interface Column {
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -50,6 +51,8 @@ export default class MatrixCheckbox extends React.Component<
|
||||||
|
|
||||||
state: MatrixState;
|
state: MatrixState;
|
||||||
sourceInvalid: boolean = false;
|
sourceInvalid: boolean = false;
|
||||||
|
mounted: boolean = false;
|
||||||
|
|
||||||
constructor(props: MatrixProps) {
|
constructor(props: MatrixProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
@ -61,12 +64,17 @@ export default class MatrixCheckbox extends React.Component<
|
||||||
|
|
||||||
this.toggleItem = this.toggleItem.bind(this);
|
this.toggleItem = this.toggleItem.bind(this);
|
||||||
this.reload = this.reload.bind(this);
|
this.reload = this.reload.bind(this);
|
||||||
|
this.initOptions = this.initOptions.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.mounted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {formInited, addHook} = this.props;
|
const {formInited, addHook} = this.props;
|
||||||
|
|
||||||
formInited ? this.reload() : addHook(this.reload, 'init');
|
formInited ? this.reload() : addHook(this.initOptions, 'init');
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps: MatrixProps) {
|
componentWillReceiveProps(nextProps: MatrixProps) {
|
||||||
|
@ -104,7 +112,24 @@ export default class MatrixCheckbox extends React.Component<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reload() {
|
componentWillUnmount() {
|
||||||
|
this.mounted = false;
|
||||||
|
const {removeHook} = this.props;
|
||||||
|
removeHook(this.initOptions, 'init');
|
||||||
|
}
|
||||||
|
|
||||||
|
async initOptions(data: any) {
|
||||||
|
await this.reload();
|
||||||
|
const {formItem, name} = this.props;
|
||||||
|
if (!formItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (formItem.value) {
|
||||||
|
setVariable(data, name!, formItem.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async reload() {
|
||||||
const {source, data, env, onChange} = this.props;
|
const {source, data, env, onChange} = this.props;
|
||||||
|
|
||||||
if (!isEffectiveApi(source, data) || this.state.loading) {
|
if (!isEffectiveApi(source, data) || this.state.loading) {
|
||||||
|
@ -115,45 +140,61 @@ export default class MatrixCheckbox extends React.Component<
|
||||||
throw new Error('fetcher is required');
|
throw new Error('fetcher is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 需要联动加载吗?我看不一定会用到,先这样吧。
|
// todo 优化这块
|
||||||
this.setState(
|
return await new Promise((resolve, reject) => {
|
||||||
{
|
if (!this.mounted) {
|
||||||
loading: true
|
return resolve();
|
||||||
},
|
|
||||||
() => {
|
|
||||||
env
|
|
||||||
.fetcher(source, data)
|
|
||||||
.then(ret => {
|
|
||||||
if (!ret.ok) {
|
|
||||||
throw new Error(ret.msg || '数据请求错误');
|
|
||||||
}
|
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
loading: false,
|
|
||||||
rows: (ret.data as any).rows || [],
|
|
||||||
columns: (ret.data as any).columns || []
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
let value = (ret.data as any).value;
|
|
||||||
if (value) {
|
|
||||||
value = mergeValue(
|
|
||||||
value,
|
|
||||||
this.state.columns,
|
|
||||||
this.state.rows
|
|
||||||
);
|
|
||||||
onChange(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch(reason =>
|
|
||||||
this.setState({
|
|
||||||
error: reason,
|
|
||||||
loading: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
loading: true
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (!this.mounted) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
env
|
||||||
|
.fetcher(source, data)
|
||||||
|
.then(ret => {
|
||||||
|
if (!ret.ok) {
|
||||||
|
throw new Error(ret.msg || '数据请求错误');
|
||||||
|
}
|
||||||
|
if (!this.mounted) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
loading: false,
|
||||||
|
rows: (ret.data as any).rows || [],
|
||||||
|
columns: (ret.data as any).columns || []
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
let value = (ret.data as any).value;
|
||||||
|
if (value) {
|
||||||
|
value = mergeValue(
|
||||||
|
value,
|
||||||
|
this.state.columns,
|
||||||
|
this.state.rows
|
||||||
|
);
|
||||||
|
onChange(value);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(reason =>
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
error: reason,
|
||||||
|
loading: false
|
||||||
|
},
|
||||||
|
resolve
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleItem(checked: boolean, x: number, y: number) {
|
toggleItem(checked: boolean, x: number, y: number) {
|
||||||
|
|
|
@ -308,12 +308,7 @@ export default class NestedSelectControl extends React.Component<
|
||||||
|
|
||||||
if (popOverContainer) {
|
if (popOverContainer) {
|
||||||
return (
|
return (
|
||||||
<Overlay
|
<Overlay container={popOverContainer} target={() => this.target} show>
|
||||||
container={popOverContainer}
|
|
||||||
placement="left-bottom-left-top right-bottom-right-top"
|
|
||||||
target={() => this.target}
|
|
||||||
show
|
|
||||||
>
|
|
||||||
<PopOver
|
<PopOver
|
||||||
className={cx('NestedSelect-popover')}
|
className={cx('NestedSelect-popover')}
|
||||||
style={{minWidth: this.target.offsetWidth}}
|
style={{minWidth: this.target.offsetWidth}}
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
|
/**
|
||||||
|
* @file 所有列表选择类控件的父级,比如 Select、Radios、Checkboxes、
|
||||||
|
* List、ButtonGroup 等等
|
||||||
|
*/
|
||||||
import {Api, Schema} from '../../types';
|
import {Api, Schema} from '../../types';
|
||||||
|
import {isEffectiveApi, isApiOutdated} from '../../utils/api';
|
||||||
import {
|
import {
|
||||||
buildApi,
|
anyChanged,
|
||||||
isEffectiveApi,
|
autobind,
|
||||||
isValidApi,
|
createObject,
|
||||||
isApiOutdated
|
setVariable,
|
||||||
} from '../../utils/api';
|
spliceTree,
|
||||||
import {anyChanged, autobind} from '../../utils/helper';
|
findTreeIndex,
|
||||||
|
getTree
|
||||||
|
} from '../../utils/helper';
|
||||||
import {reaction} from 'mobx';
|
import {reaction} from 'mobx';
|
||||||
import {FormControlProps, registerFormItem, FormItemBasicConfig} from './Item';
|
import {FormControlProps, registerFormItem, FormItemBasicConfig} from './Item';
|
||||||
import {IFormItemStore} from '../../store/formItem';
|
import {IFormItemStore} from '../../store/formItem';
|
||||||
|
@ -13,8 +20,9 @@ export type OptionsControlComponent = React.ComponentType<FormControlProps>;
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {resolveVariableAndFilter} from '../../utils/tpl-builtin';
|
import {resolveVariableAndFilter} from '../../utils/tpl-builtin';
|
||||||
import {evalExpression} from '../../utils/tpl';
|
|
||||||
import {Option, OptionProps, normalizeOptions} from '../../components/Select';
|
import {Option, OptionProps, normalizeOptions} from '../../components/Select';
|
||||||
|
import {filter} from '../../utils/tpl';
|
||||||
|
import findIndex from 'lodash/findIndex';
|
||||||
|
|
||||||
export {Option};
|
export {Option};
|
||||||
|
|
||||||
|
@ -26,6 +34,7 @@ export interface OptionsConfig extends OptionsBasicConfig {
|
||||||
component: React.ComponentType<OptionsControlProps>;
|
component: React.ComponentType<OptionsControlProps>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 下发给注册进来的组件的属性。
|
||||||
export interface OptionsControlProps extends FormControlProps, OptionProps {
|
export interface OptionsControlProps extends FormControlProps, OptionProps {
|
||||||
source?: Api;
|
source?: Api;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
@ -35,30 +44,36 @@ export interface OptionsControlProps extends FormControlProps, OptionProps {
|
||||||
setOptions: (value: Array<any>) => void;
|
setOptions: (value: Array<any>) => void;
|
||||||
setLoading: (value: boolean) => void;
|
setLoading: (value: boolean) => void;
|
||||||
reloadOptions: () => void;
|
reloadOptions: () => void;
|
||||||
addable?: boolean;
|
creatable?: boolean;
|
||||||
onAdd?: () => void;
|
onAdd?: (
|
||||||
|
idx?: number | Array<number>,
|
||||||
|
value?: any,
|
||||||
|
skipForm?: boolean
|
||||||
|
) => void;
|
||||||
|
addControls?: Array<any>;
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
onEdit?: (value: Option) => void;
|
editControls?: Array<any>;
|
||||||
|
onEdit?: (value: Option, origin?: Option, skipForm?: boolean) => void;
|
||||||
removable?: boolean;
|
removable?: boolean;
|
||||||
onDelete?: (value: Option) => void;
|
onDelete?: (value: Option) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 自己接收的属性。
|
||||||
export interface OptionsProps extends FormControlProps, OptionProps {
|
export interface OptionsProps extends FormControlProps, OptionProps {
|
||||||
sourcce?: Api;
|
source?: Api;
|
||||||
|
creatable?: boolean;
|
||||||
addApi?: Api;
|
addApi?: Api;
|
||||||
addMode?: 'dialog' | 'normal';
|
addControls?: Array<any>;
|
||||||
addDialog?: Schema;
|
|
||||||
editApi?: Api;
|
editApi?: Api;
|
||||||
editMode?: 'dialog' | 'normal';
|
editControls?: Array<any>;
|
||||||
editDialog?: Schema;
|
|
||||||
deleteApi?: Api;
|
deleteApi?: Api;
|
||||||
deleteConfirmText?: string;
|
deleteConfirmText?: string;
|
||||||
|
optionLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerOptionsControl(config: OptionsConfig) {
|
export function registerOptionsControl(config: OptionsConfig) {
|
||||||
const Control = config.component;
|
const Control = config.component;
|
||||||
|
|
||||||
// @observer
|
|
||||||
class FormOptionsItem extends React.Component<OptionsProps, any> {
|
class FormOptionsItem extends React.Component<OptionsProps, any> {
|
||||||
static displayName = `OptionsControl(${config.type})`;
|
static displayName = `OptionsControl(${config.type})`;
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -70,6 +85,7 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||||
multiple: false,
|
multiple: false,
|
||||||
placeholder: '请选择',
|
placeholder: '请选择',
|
||||||
resetValue: '',
|
resetValue: '',
|
||||||
|
deleteConfirmText: '确定要删除?',
|
||||||
...Control.defaultProps
|
...Control.defaultProps
|
||||||
};
|
};
|
||||||
static propsList: any = (Control as any).propsList
|
static propsList: any = (Control as any).propsList
|
||||||
|
@ -113,10 +129,10 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||||
|
|
||||||
let loadOptions: boolean = initFetch !== false;
|
let loadOptions: boolean = initFetch !== false;
|
||||||
|
|
||||||
if (/^\$(?:([a-z0-9_.]+)|{.+})$/.test(source) && formItem) {
|
if (/^\$(?:([a-z0-9_.]+)|{.+})$/.test(source as string) && formItem) {
|
||||||
formItem.setOptions(
|
formItem.setOptions(
|
||||||
normalizeOptions(
|
normalizeOptions(
|
||||||
resolveVariableAndFilter(source, data, '| raw') || []
|
resolveVariableAndFilter(source as string, data, '| raw') || []
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
loadOptions = false;
|
loadOptions = false;
|
||||||
|
@ -134,7 +150,9 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOptions &&
|
loadOptions &&
|
||||||
(formInited ? this.reload() : addHook && addHook(this.reload, 'init'));
|
(formInited
|
||||||
|
? this.reload()
|
||||||
|
: addHook && addHook(this.initOptions, 'init'));
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -207,7 +225,7 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||||
) {
|
) {
|
||||||
if (/^\$(?:([a-z0-9_.]+)|{.+})$/.test(props.source as string)) {
|
if (/^\$(?:([a-z0-9_.]+)|{.+})$/.test(props.source as string)) {
|
||||||
const prevOptions = resolveVariableAndFilter(
|
const prevOptions = resolveVariableAndFilter(
|
||||||
prevProps.source,
|
prevProps.source as string,
|
||||||
prevProps.data,
|
prevProps.data,
|
||||||
'| raw'
|
'| raw'
|
||||||
);
|
);
|
||||||
|
@ -417,6 +435,18 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||||
return formItem.loadOptions(source, data, undefined, false, onChange);
|
return formItem.loadOptions(source, data, undefined, false, onChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
async initOptions(data: any) {
|
||||||
|
await this.reload();
|
||||||
|
const {formItem, name} = this.props;
|
||||||
|
if (!formItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (formItem.value) {
|
||||||
|
setVariable(data, name!, formItem.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
this.input && this.input.focus && this.input.focus();
|
this.input && this.input.focus && this.input.focus();
|
||||||
}
|
}
|
||||||
|
@ -439,8 +469,280 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||||
formItem && formItem.setLoading(value);
|
formItem && formItem.setLoading(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
async handleOptionAdd(
|
||||||
|
idx: number | Array<number> = -1,
|
||||||
|
value: any,
|
||||||
|
skipForm: boolean = false
|
||||||
|
) {
|
||||||
|
let {
|
||||||
|
addControls,
|
||||||
|
disabled,
|
||||||
|
labelField,
|
||||||
|
onOpenDialog,
|
||||||
|
optionLabel,
|
||||||
|
addApi,
|
||||||
|
source,
|
||||||
|
data,
|
||||||
|
valueField,
|
||||||
|
formItem: model,
|
||||||
|
createBtnLabel,
|
||||||
|
env
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
// 禁用或者没有配置 name
|
||||||
|
if (disabled || !model) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户没有配置表单项,则自动创建一个 label 输入
|
||||||
|
if (!skipForm && (!Array.isArray(addControls) || !addControls.length)) {
|
||||||
|
addControls = [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: labelField || 'label',
|
||||||
|
label: false,
|
||||||
|
placeholder: '请输入名称'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
const ctx = createObject(
|
||||||
|
data,
|
||||||
|
Array.isArray(idx)
|
||||||
|
? {
|
||||||
|
parent: getTree(model.options, idx.slice(0, idx.length - 1)),
|
||||||
|
...value
|
||||||
|
}
|
||||||
|
: value
|
||||||
|
);
|
||||||
|
|
||||||
|
let result: any = skipForm
|
||||||
|
? ctx
|
||||||
|
: await onOpenDialog(
|
||||||
|
{
|
||||||
|
type: 'dialog',
|
||||||
|
title: createBtnLabel || `新增${optionLabel || '选项'}`,
|
||||||
|
body: {
|
||||||
|
type: 'form',
|
||||||
|
api: addApi,
|
||||||
|
controls: addControls
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ctx
|
||||||
|
);
|
||||||
|
|
||||||
|
// 单独发请求
|
||||||
|
if (skipForm && addApi) {
|
||||||
|
try {
|
||||||
|
const payload = await env.fetcher(addApi!, result, {
|
||||||
|
method: 'post'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!payload.ok) {
|
||||||
|
env.notify('error', payload.msg || '新增失败,请仔细检查');
|
||||||
|
} else {
|
||||||
|
result = payload.data || result;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
result = null;
|
||||||
|
console.error(e);
|
||||||
|
env.notify('error', e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有 result 说明弹框点了确认。否则就是取消了。
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没走服务端的。
|
||||||
|
if (!result.__saved) {
|
||||||
|
result = {
|
||||||
|
...result,
|
||||||
|
[valueField || 'value']: result[labelField || 'label']
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果配置了 source 直接重新拉取接口就够了
|
||||||
|
if (source) {
|
||||||
|
this.reload();
|
||||||
|
} else {
|
||||||
|
// 否则直接前端变更 options
|
||||||
|
let options = model.options.concat();
|
||||||
|
if (Array.isArray(idx)) {
|
||||||
|
options = spliceTree(options, idx, 0, {...result});
|
||||||
|
} else {
|
||||||
|
~idx
|
||||||
|
? options.splice(idx, 0, {...result})
|
||||||
|
: options.push({...result});
|
||||||
|
}
|
||||||
|
model.setOptions(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
async handleOptionEdit(
|
||||||
|
value: any,
|
||||||
|
origin: any = value,
|
||||||
|
skipForm: boolean = false
|
||||||
|
) {
|
||||||
|
let {
|
||||||
|
editControls,
|
||||||
|
disabled,
|
||||||
|
labelField,
|
||||||
|
onOpenDialog,
|
||||||
|
editApi,
|
||||||
|
env,
|
||||||
|
source,
|
||||||
|
data,
|
||||||
|
formItem: model,
|
||||||
|
optionLabel
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (disabled || !model) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skipForm && (!Array.isArray(editControls) || !editControls.length)) {
|
||||||
|
editControls = [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: labelField || 'label',
|
||||||
|
label: false,
|
||||||
|
placeholder: '请输入名称'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = skipForm
|
||||||
|
? value
|
||||||
|
: await onOpenDialog(
|
||||||
|
{
|
||||||
|
type: 'dialog',
|
||||||
|
title: `编辑${optionLabel || '选项'}`,
|
||||||
|
body: {
|
||||||
|
type: 'form',
|
||||||
|
api: editApi,
|
||||||
|
controls: editControls
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createObject(data, value)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 单独发请求
|
||||||
|
if (skipForm && editApi) {
|
||||||
|
try {
|
||||||
|
const payload = await env.fetcher(
|
||||||
|
editApi!,
|
||||||
|
createObject(data, result),
|
||||||
|
{
|
||||||
|
method: 'post'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!payload.ok) {
|
||||||
|
env.notify('error', payload.msg || '保存失败,请仔细检查');
|
||||||
|
} else {
|
||||||
|
result = payload.data || result;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
result = null;
|
||||||
|
console.error(e);
|
||||||
|
env.notify('error', e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有结果,说明取消了。
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source) {
|
||||||
|
this.reload();
|
||||||
|
} else {
|
||||||
|
const indexes = findTreeIndex(model.options, item => item === origin);
|
||||||
|
|
||||||
|
if (indexes) {
|
||||||
|
model.setOptions(
|
||||||
|
spliceTree(model.options, indexes, 1, {
|
||||||
|
...origin,
|
||||||
|
...result
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
|
||||||
|
// 如果配置了 deleteConfirmText 让用户先确认。
|
||||||
|
const confirmed = deleteConfirmText
|
||||||
|
? await env.confirm(filter(deleteConfirmText, ctx))
|
||||||
|
: true;
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过 deleteApi 删除。
|
||||||
|
try {
|
||||||
|
if (!deleteApi) {
|
||||||
|
throw new Error('请配置 deleteApi');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await env.fetcher(deleteApi!, ctx, {
|
||||||
|
method: 'delete'
|
||||||
|
});
|
||||||
|
|
||||||
|
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() {
|
render() {
|
||||||
const {value, formItem} = this.props;
|
const {
|
||||||
|
value,
|
||||||
|
formItem,
|
||||||
|
addApi,
|
||||||
|
editApi,
|
||||||
|
deleteApi,
|
||||||
|
creatable,
|
||||||
|
editable,
|
||||||
|
removable
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Control
|
<Control
|
||||||
|
@ -455,6 +757,12 @@ export function registerOptionsControl(config: OptionsConfig) {
|
||||||
setOptions={this.setOptions}
|
setOptions={this.setOptions}
|
||||||
syncOptions={this.syncOptions}
|
syncOptions={this.syncOptions}
|
||||||
reloadOptions={this.reload}
|
reloadOptions={this.reload}
|
||||||
|
creatable={creatable || isEffectiveApi(addApi)}
|
||||||
|
editable={editable || isEffectiveApi(editApi)}
|
||||||
|
removable={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
|
leading: false
|
||||||
});
|
});
|
||||||
this.inputRef = this.inputRef.bind(this);
|
this.inputRef = this.inputRef.bind(this);
|
||||||
this.handleNewOptionClick = this.handleNewOptionClick.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inputRef(ref: any) {
|
inputRef(ref: any) {
|
||||||
|
@ -51,6 +50,7 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
||||||
delimiter,
|
delimiter,
|
||||||
multiple,
|
multiple,
|
||||||
type,
|
type,
|
||||||
|
valueField,
|
||||||
onChange,
|
onChange,
|
||||||
setOptions,
|
setOptions,
|
||||||
options,
|
options,
|
||||||
|
@ -63,7 +63,11 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
||||||
|
|
||||||
(Array.isArray(value) ? value : value ? [value] : []).forEach(
|
(Array.isArray(value) ? value : value ? [value] : []).forEach(
|
||||||
(option: any) => {
|
(option: any) => {
|
||||||
let resolved = find(options, (item: any) => item.value == option.value);
|
let resolved = find(
|
||||||
|
options,
|
||||||
|
(item: any) =>
|
||||||
|
item[valueField || 'value'] == option[valueField || 'value']
|
||||||
|
);
|
||||||
resolved || additonalOptions.push(option);
|
resolved || additonalOptions.push(option);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -71,22 +75,24 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
||||||
if (joinValues) {
|
if (joinValues) {
|
||||||
if (multiple) {
|
if (multiple) {
|
||||||
newValue = Array.isArray(value)
|
newValue = Array.isArray(value)
|
||||||
? (value.map(item => item.value).join(delimiter) as string)
|
? (value
|
||||||
|
.map(item => item[valueField || 'value'])
|
||||||
|
.join(delimiter) as string)
|
||||||
: value
|
: value
|
||||||
? (value as Option).value
|
? (value as Option)[valueField || 'value']
|
||||||
: '';
|
: '';
|
||||||
} else {
|
} else {
|
||||||
newValue = newValue ? (newValue as Option).value : '';
|
newValue = newValue ? (newValue as Option)[valueField || 'value'] : '';
|
||||||
}
|
}
|
||||||
} else if (extractValue) {
|
} else if (extractValue) {
|
||||||
if (multiple) {
|
if (multiple) {
|
||||||
newValue = Array.isArray(value)
|
newValue = Array.isArray(value)
|
||||||
? value.map(item => item.value)
|
? value.map(item => item[valueField || 'value'])
|
||||||
: value
|
: value
|
||||||
? [(value as Option).value]
|
? [(value as Option)[valueField || 'value']]
|
||||||
: [''];
|
: [''];
|
||||||
} else {
|
} else {
|
||||||
newValue = newValue ? (newValue as Option).value : '';
|
newValue = newValue ? (newValue as Option)[valueField || 'value'] : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,16 +167,6 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
||||||
return combinedOptions;
|
return combinedOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewOptionClick(option: any) {
|
|
||||||
const {setOptions, options} = this.props;
|
|
||||||
|
|
||||||
let mergedOptions: Array<any> = options.concat();
|
|
||||||
mergedOptions.push({
|
|
||||||
...option
|
|
||||||
});
|
|
||||||
setOptions(mergedOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
reload() {
|
reload() {
|
||||||
const reload = this.props.reloadOptions;
|
const reload = this.props.reloadOptions;
|
||||||
reload && reload();
|
reload && reload();
|
||||||
|
@ -211,12 +207,11 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
||||||
ref={this.inputRef}
|
ref={this.inputRef}
|
||||||
value={selectedOptions}
|
value={selectedOptions}
|
||||||
options={options}
|
options={options}
|
||||||
onNewOptionClick={this.handleNewOptionClick}
|
|
||||||
loadOptions={
|
loadOptions={
|
||||||
isEffectiveApi(autoComplete) ? this.loadRemote : undefined
|
isEffectiveApi(autoComplete) ? this.loadRemote : undefined
|
||||||
}
|
}
|
||||||
creatable={creatable}
|
creatable={creatable}
|
||||||
searchable={autoComplete || creatable ? true : searchable}
|
searchable={searchable || !!autoComplete}
|
||||||
onChange={this.changeValue}
|
onChange={this.changeValue}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
noResultsText={noResultsText}
|
noResultsText={noResultsText}
|
||||||
|
|
|
@ -3,12 +3,13 @@ import {OptionsControl, OptionsControlProps, highlight} from './Options';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import {Action} from '../../types';
|
import {Action} from '../../types';
|
||||||
import Downshift, {StateChangeOptions} from 'downshift';
|
import Downshift, {StateChangeOptions} from 'downshift';
|
||||||
|
// @ts-ignore
|
||||||
import matchSorter from 'match-sorter';
|
import matchSorter from 'match-sorter';
|
||||||
import debouce = require('lodash/debounce');
|
import debouce = require('lodash/debounce');
|
||||||
import {filter} from '../../utils/tpl';
|
import {filter} from '../../utils/tpl';
|
||||||
import find = require('lodash/find');
|
import find = require('lodash/find');
|
||||||
import {Icon} from '../../components/icons';
|
import {Icon} from '../../components/icons';
|
||||||
import {autobind, createObject} from '../../utils/helper';
|
import {autobind, createObject, setVariable} from '../../utils/helper';
|
||||||
import {isEffectiveApi} from '../../utils/api';
|
import {isEffectiveApi} from '../../utils/api';
|
||||||
|
|
||||||
// declare function matchSorter(items:Array<any>, input:any, options:any): Array<any>;
|
// declare function matchSorter(items:Array<any>, input:any, options:any): Array<any>;
|
||||||
|
@ -88,7 +89,7 @@ export default class TextControl extends React.PureComponent<
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {formItem, autoComplete, data, addHook, formInited} = this.props;
|
const {formItem, autoComplete, addHook, formInited, data} = this.props;
|
||||||
|
|
||||||
if (isEffectiveApi(autoComplete, data) && formItem) {
|
if (isEffectiveApi(autoComplete, data) && formItem) {
|
||||||
if (formInited) {
|
if (formInited) {
|
||||||
|
@ -99,16 +100,18 @@ export default class TextControl extends React.PureComponent<
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.unHook = addHook(
|
this.unHook = addHook(async (data: any) => {
|
||||||
() =>
|
await formItem.loadOptions(
|
||||||
formItem.loadOptions(
|
autoComplete,
|
||||||
autoComplete,
|
createObject(data, {
|
||||||
createObject(data, {
|
term: ''
|
||||||
term: ''
|
})
|
||||||
})
|
);
|
||||||
),
|
|
||||||
'init'
|
if (formItem.value) {
|
||||||
);
|
setVariable(data, name!, formItem.value);
|
||||||
|
}
|
||||||
|
}, 'init');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -447,7 +450,7 @@ export default class TextControl extends React.PureComponent<
|
||||||
filtedOptions.push({
|
filtedOptions.push({
|
||||||
[labelField || 'label']: this.state.inputValue,
|
[labelField || 'label']: this.state.inputValue,
|
||||||
[valueField || 'value']: this.state.inputValue,
|
[valueField || 'value']: this.state.inputValue,
|
||||||
isNew: true
|
'isNew': true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,6 @@ import React from 'react';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import TreeSelector from '../../components/Tree';
|
import TreeSelector from '../../components/Tree';
|
||||||
import {OptionsControl, OptionsControlProps} from './Options';
|
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';
|
|
||||||
import {Spinner} from '../../components';
|
import {Spinner} from '../../components';
|
||||||
|
|
||||||
export interface TreeProps extends OptionsControlProps {
|
export interface TreeProps extends OptionsControlProps {
|
||||||
|
@ -18,172 +13,25 @@ export interface TreeProps extends OptionsControlProps {
|
||||||
cascade?: boolean; // 父子之间是否完全独立。
|
cascade?: boolean; // 父子之间是否完全独立。
|
||||||
withChildren?: boolean; // 选父级的时候是否把子节点的值也包含在内。
|
withChildren?: boolean; // 选父级的时候是否把子节点的值也包含在内。
|
||||||
onlyChildren?: boolean; // 选父级的时候,是否只把子节点的值包含在内
|
onlyChildren?: boolean; // 选父级的时候,是否只把子节点的值包含在内
|
||||||
addApi?: Api;
|
addControls?: Array<any>;
|
||||||
addMode?: 'dialog' | 'normal';
|
updateControls?: Array<any>;
|
||||||
addDialog?: Schema;
|
rootCreatable?: boolean;
|
||||||
editApi?: Api;
|
|
||||||
editMode?: 'dialog' | 'normal';
|
|
||||||
editDialog?: Schema;
|
|
||||||
deleteApi?: Api;
|
|
||||||
deleteConfirmText?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TreeState {
|
export default class TreeControl extends React.Component<TreeProps> {
|
||||||
isAddModalOpened: boolean;
|
|
||||||
isEditModalOpened: boolean;
|
|
||||||
parent: Option | null;
|
|
||||||
prev: Option | null;
|
|
||||||
data: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class TreeControl extends React.Component<TreeProps, TreeState> {
|
|
||||||
static defaultProps: Partial<TreeProps> = {
|
static defaultProps: Partial<TreeProps> = {
|
||||||
placeholder: '选项加载中...',
|
placeholder: '选项加载中...',
|
||||||
multiple: false,
|
multiple: false,
|
||||||
hideRoot: false,
|
|
||||||
rootLabel: '顶级',
|
rootLabel: '顶级',
|
||||||
rootValue: '',
|
rootValue: '',
|
||||||
showIcon: true
|
showIcon: true
|
||||||
};
|
};
|
||||||
|
|
||||||
state: TreeState = {
|
|
||||||
isAddModalOpened: false,
|
|
||||||
isEditModalOpened: false,
|
|
||||||
parent: null,
|
|
||||||
prev: null,
|
|
||||||
data: null
|
|
||||||
};
|
|
||||||
|
|
||||||
reload() {
|
reload() {
|
||||||
const reload = this.props.reloadOptions;
|
const reload = this.props.reloadOptions;
|
||||||
reload && reload();
|
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', result.msg || '删除失败');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
openAddDialog(parent: Option | null) {
|
|
||||||
const {data} = this.props;
|
|
||||||
this.setState({
|
|
||||||
isAddModalOpened: true,
|
|
||||||
data: createObject(data, parent ? parent : {}),
|
|
||||||
parent
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
closeAddDialog() {
|
|
||||||
this.setState({
|
|
||||||
isAddModalOpened: false,
|
|
||||||
parent: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
openEditDialog(prev: Option) {
|
|
||||||
const {data} = this.props;
|
|
||||||
this.setState({
|
|
||||||
isEditModalOpened: true,
|
|
||||||
data: createObject(data, prev),
|
|
||||||
prev
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@autobind
|
|
||||||
closeEditDialog() {
|
|
||||||
this.setState({
|
|
||||||
isEditModalOpened: false,
|
|
||||||
prev: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
|
@ -196,7 +44,6 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
|
||||||
delimiter,
|
delimiter,
|
||||||
placeholder,
|
placeholder,
|
||||||
options,
|
options,
|
||||||
inline,
|
|
||||||
multiple,
|
multiple,
|
||||||
valueField,
|
valueField,
|
||||||
initiallyOpen,
|
initiallyOpen,
|
||||||
|
@ -210,18 +57,17 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
|
||||||
rootValue,
|
rootValue,
|
||||||
showIcon,
|
showIcon,
|
||||||
showRadio,
|
showRadio,
|
||||||
render,
|
onAdd,
|
||||||
addMode,
|
creatable,
|
||||||
addApi,
|
addControls,
|
||||||
addDialog,
|
onEdit,
|
||||||
editMode,
|
editable,
|
||||||
editApi,
|
editControls,
|
||||||
editDialog,
|
removable,
|
||||||
deleteApi
|
onDelete,
|
||||||
|
rootCreatable
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {data} = this.state;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(`${ns}TreeControl`, className)}>
|
<div className={cx(`${ns}TreeControl`, className)}>
|
||||||
<Spinner size="sm" key="info" show={loading} />
|
<Spinner size="sm" key="info" show={loading} />
|
||||||
|
@ -235,7 +81,7 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
|
||||||
extractValue={extractValue}
|
extractValue={extractValue}
|
||||||
delimiter={delimiter}
|
delimiter={delimiter}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
data={options}
|
options={options}
|
||||||
multiple={multiple}
|
multiple={multiple}
|
||||||
initiallyOpen={initiallyOpen}
|
initiallyOpen={initiallyOpen}
|
||||||
unfoldedLevel={unfoldedLevel}
|
unfoldedLevel={unfoldedLevel}
|
||||||
|
@ -249,52 +95,18 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
|
||||||
cascade={cascade}
|
cascade={cascade}
|
||||||
foldedField="collapsed"
|
foldedField="collapsed"
|
||||||
value={value || ''}
|
value={value || ''}
|
||||||
nameField="label"
|
labelField="label"
|
||||||
selfDisabledAffectChildren={false}
|
selfDisabledAffectChildren={false}
|
||||||
addMode={addMode}
|
onAdd={onAdd}
|
||||||
addable={isEffectiveApi(addApi)}
|
creatable={creatable}
|
||||||
onAdd={this.handleAdd}
|
rootCreatable={rootCreatable}
|
||||||
openAddDialog={this.openAddDialog}
|
onEdit={onEdit}
|
||||||
editMode={editMode}
|
editable={editable}
|
||||||
editable={isEffectiveApi(editApi)}
|
removable={removable}
|
||||||
onEdit={this.handleEdit}
|
onDelete={onDelete}
|
||||||
openEditDialog={this.openEditDialog}
|
bultinCUD={!addControls && !editControls}
|
||||||
onRemove={this.handleRemove}
|
|
||||||
removable={isEffectiveApi(deleteApi)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{addMode &&
|
|
||||||
render(
|
|
||||||
'modal',
|
|
||||||
{
|
|
||||||
type: 'dialog',
|
|
||||||
...addDialog
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'addModal',
|
|
||||||
data: data,
|
|
||||||
onConfirm: this.handleAddModalConfirm,
|
|
||||||
onClose: this.closeAddDialog,
|
|
||||||
show: this.state.isAddModalOpened
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
|
|
||||||
{editMode &&
|
|
||||||
render(
|
|
||||||
'modal',
|
|
||||||
{
|
|
||||||
type: 'dialog',
|
|
||||||
...editDialog
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'editModal',
|
|
||||||
data: data,
|
|
||||||
onConfirm: this.handleEditModalConfirm,
|
|
||||||
onClose: this.closeEditDialog,
|
|
||||||
show: this.state.isEditModalOpened
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import PopOver from '../../components/PopOver';
|
||||||
import {OptionsControl, OptionsControlProps, Option} from './Options';
|
import {OptionsControl, OptionsControlProps, Option} from './Options';
|
||||||
import {Icon} from '../../components/icons';
|
import {Icon} from '../../components/icons';
|
||||||
import TreeSelector from '../../components/Tree';
|
import TreeSelector from '../../components/Tree';
|
||||||
|
// @ts-ignore
|
||||||
import matchSorter from 'match-sorter';
|
import matchSorter from 'match-sorter';
|
||||||
import debouce = require('lodash/debounce');
|
import debouce = require('lodash/debounce');
|
||||||
import find = require('lodash/find');
|
import find = require('lodash/find');
|
||||||
|
@ -395,7 +396,6 @@ export default class TreeSelectControl extends React.Component<
|
||||||
return (
|
return (
|
||||||
<Overlay
|
<Overlay
|
||||||
container={popOverContainer || (() => this.container.current)}
|
container={popOverContainer || (() => this.container.current)}
|
||||||
placement="left-bottom-left-top right-bottom-right-top"
|
|
||||||
target={() => this.target.current}
|
target={() => this.target.current}
|
||||||
show
|
show
|
||||||
>
|
>
|
||||||
|
@ -420,7 +420,7 @@ export default class TreeSelectControl extends React.Component<
|
||||||
extractValue={extractValue}
|
extractValue={extractValue}
|
||||||
delimiter={delimiter}
|
delimiter={delimiter}
|
||||||
placeholder={optionsPlaceholder}
|
placeholder={optionsPlaceholder}
|
||||||
data={filtedOptions}
|
options={filtedOptions}
|
||||||
highlightTxt={this.state.inputValue}
|
highlightTxt={this.state.inputValue}
|
||||||
multiple={multiple}
|
multiple={multiple}
|
||||||
initiallyOpen={initiallyOpen}
|
initiallyOpen={initiallyOpen}
|
||||||
|
@ -434,7 +434,7 @@ export default class TreeSelectControl extends React.Component<
|
||||||
foldedField="collapsed"
|
foldedField="collapsed"
|
||||||
hideRoot
|
hideRoot
|
||||||
value={value || ''}
|
value={value || ''}
|
||||||
nameField="label"
|
labelField="label"
|
||||||
maxLength={maxLength}
|
maxLength={maxLength}
|
||||||
minLength={minLength}
|
minLength={minLength}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -13,7 +13,9 @@ import {
|
||||||
until,
|
until,
|
||||||
noop,
|
noop,
|
||||||
isObject,
|
isObject,
|
||||||
isVisible
|
isVisible,
|
||||||
|
createObject,
|
||||||
|
extendObject
|
||||||
} from '../../utils/helper';
|
} from '../../utils/helper';
|
||||||
import debouce = require('lodash/debounce');
|
import debouce = require('lodash/debounce');
|
||||||
import flatten = require('lodash/flatten');
|
import flatten = require('lodash/flatten');
|
||||||
|
@ -314,12 +316,16 @@ export default class Form extends React.Component<FormProps, object> {
|
||||||
async onInit() {
|
async onInit() {
|
||||||
const {onInit, store, submitOnInit} = this.props;
|
const {onInit, store, submitOnInit} = this.props;
|
||||||
|
|
||||||
const data = store.data;
|
// 先拿出来数据,主要担心 form 被什么东西篡改了,然后又应用出去了
|
||||||
|
// 之前遇到过问题,所以拿出来了。但是 options loadOptions 默认值失效了。
|
||||||
|
// 所以目前需要两个都要设置一下,再 init Hook 里面。
|
||||||
|
const data = {...store.data};
|
||||||
|
|
||||||
store.setInited(true);
|
store.setInited(true);
|
||||||
const hooks: Array<(data: any) => Promise<any>> = this.hooks['init'] || [];
|
const hooks: Array<(data: any) => Promise<any>> = this.hooks['init'] || [];
|
||||||
await Promise.all(hooks.map(hook => hook(data)));
|
await Promise.all(hooks.map(hook => hook(data)));
|
||||||
|
|
||||||
onInit && onInit(data);
|
onInit && onInit(extendObject(store.data, data));
|
||||||
|
|
||||||
submitOnInit &&
|
submitOnInit &&
|
||||||
this.handleAction(
|
this.handleAction(
|
||||||
|
|
|
@ -421,7 +421,6 @@ export const HocQuickEdit = (config: Partial<QuickEditConfig> = {}) => (
|
||||||
return (
|
return (
|
||||||
<Overlay
|
<Overlay
|
||||||
container={popOverContainer}
|
container={popOverContainer}
|
||||||
placement="left-top right-top left-bottom right-bottom"
|
|
||||||
target={() => this.target}
|
target={() => this.target}
|
||||||
onHide={this.closeQuickEdit}
|
onHide={this.closeQuickEdit}
|
||||||
show
|
show
|
||||||
|
|
|
@ -5,17 +5,12 @@ import {Api, Payload, fetchOptions} from '../types';
|
||||||
import {ComboStore, IComboStore, IUniqueGroup} from './combo';
|
import {ComboStore, IComboStore, IUniqueGroup} from './combo';
|
||||||
import {evalExpression} from '../utils/tpl';
|
import {evalExpression} from '../utils/tpl';
|
||||||
import findIndex = require('lodash/findIndex');
|
import findIndex = require('lodash/findIndex');
|
||||||
import {
|
import {isArrayChilrenModified, isObject, createObject} from '../utils/helper';
|
||||||
isArrayChilrenModified,
|
|
||||||
hasOwnProperty,
|
|
||||||
isObject,
|
|
||||||
createObject
|
|
||||||
} from '../utils/helper';
|
|
||||||
import {flattenTree} from '../utils/helper';
|
import {flattenTree} from '../utils/helper';
|
||||||
import {IRendererStore} from '.';
|
import {IRendererStore} from '.';
|
||||||
import {normalizeOptions} from '../components/Select';
|
import {normalizeOptions} from '../components/Select';
|
||||||
import find = require('lodash/find');
|
import find = require('lodash/find');
|
||||||
import {iRendererStore} from './iRenderer';
|
import {SimpleMap} from '../utils/SimpleMap';
|
||||||
|
|
||||||
interface IOption {
|
interface IOption {
|
||||||
value?: string | number | null;
|
value?: string | number | null;
|
||||||
|
@ -34,6 +29,7 @@ const ErrorDetail = types.model('ErrorDetail', {
|
||||||
export const FormItemStore = types
|
export const FormItemStore = types
|
||||||
.model('FormItemStore', {
|
.model('FormItemStore', {
|
||||||
identifier: types.identifier,
|
identifier: types.identifier,
|
||||||
|
isFocused: false,
|
||||||
type: '',
|
type: '',
|
||||||
unique: false,
|
unique: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@ -81,41 +77,6 @@ export const FormItemStore = types
|
||||||
return self.errorData.map(item => item.msg);
|
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 {
|
return {
|
||||||
get form(): any {
|
get form(): any {
|
||||||
return getForm();
|
return getForm();
|
||||||
|
@ -144,9 +105,6 @@ export const FormItemStore = types
|
||||||
return getLastOptionValue();
|
return getLastOptionValue();
|
||||||
},
|
},
|
||||||
|
|
||||||
// selectedOptions,
|
|
||||||
// filteredOptions,
|
|
||||||
|
|
||||||
getSelectedOptions(value: any = getValue()) {
|
getSelectedOptions(value: any = getValue()) {
|
||||||
if (value === getValue()) {
|
if (value === getValue()) {
|
||||||
return self.selectedOptions;
|
return self.selectedOptions;
|
||||||
|
@ -212,6 +170,9 @@ export const FormItemStore = types
|
||||||
})
|
})
|
||||||
|
|
||||||
.actions(self => {
|
.actions(self => {
|
||||||
|
const form = self.form as IFormStore;
|
||||||
|
const dialogCallbacks = new SimpleMap<(result?: any) => void>();
|
||||||
|
|
||||||
function config({
|
function config({
|
||||||
required,
|
required,
|
||||||
unique,
|
unique,
|
||||||
|
@ -241,8 +202,6 @@ export const FormItemStore = types
|
||||||
type?: string;
|
type?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
}) {
|
}) {
|
||||||
const form = self.form as IFormStore;
|
|
||||||
|
|
||||||
if (typeof rules === 'string') {
|
if (typeof rules === 'string') {
|
||||||
rules = str2rules(rules);
|
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) {
|
function changeValue(value: any, isPrintine: boolean = false) {
|
||||||
if (typeof value === 'undefined' || value === '__undefined') {
|
if (typeof value === 'undefined' || value === '__undefined') {
|
||||||
self.form.deleteValueByName(self.name);
|
self.form.deleteValueByName(self.name);
|
||||||
|
@ -366,7 +333,7 @@ export const FormItemStore = types
|
||||||
options?: fetchOptions,
|
options?: fetchOptions,
|
||||||
clearValue?: boolean,
|
clearValue?: boolean,
|
||||||
onChange?: (value: any) => void
|
onChange?: (value: any) => void
|
||||||
) => Promise<any> = flow(function* getInitData(
|
) => Promise<Payload | null> = flow(function* getInitData(
|
||||||
api: string,
|
api: string,
|
||||||
data: object,
|
data: object,
|
||||||
options?: fetchOptions,
|
options?: fetchOptions,
|
||||||
|
@ -442,8 +409,9 @@ export const FormItemStore = types
|
||||||
console.error(e.stack);
|
console.error(e.stack);
|
||||||
getRoot(self) &&
|
getRoot(self) &&
|
||||||
(getRoot(self) as IRendererStore).notify('error', e.message);
|
(getRoot(self) as IRendererStore).notify('error', e.message);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
});
|
} as any);
|
||||||
|
|
||||||
function syncOptions(originOptions?: Array<any>) {
|
function syncOptions(originOptions?: Array<any>) {
|
||||||
if (!self.options.length && typeof self.value === 'undefined') {
|
if (!self.options.length && typeof self.value === 'undefined') {
|
||||||
|
@ -528,7 +496,7 @@ export const FormItemStore = types
|
||||||
unMatched = {
|
unMatched = {
|
||||||
[self.valueField || 'value']: item,
|
[self.valueField || 'value']: item,
|
||||||
[self.labelField || 'label']: item,
|
[self.labelField || 'label']: item,
|
||||||
__unmatched: true
|
'__unmatched': true
|
||||||
};
|
};
|
||||||
|
|
||||||
const orgin: any =
|
const orgin: any =
|
||||||
|
@ -595,27 +563,30 @@ export const FormItemStore = types
|
||||||
clearError();
|
clearError();
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDialog(schema: any, ctx: any, additonal?: object) {
|
function openDialog(
|
||||||
let proto = ctx.__super ? ctx.__super : self.form.data;
|
schema: any,
|
||||||
|
data: any = form.data,
|
||||||
if (additonal) {
|
callback?: (ret?: any) => void
|
||||||
proto = createObject(proto, additonal);
|
) {
|
||||||
}
|
|
||||||
|
|
||||||
const data = createObject(proto, {
|
|
||||||
...ctx
|
|
||||||
});
|
|
||||||
|
|
||||||
self.dialogSchema = schema;
|
self.dialogSchema = schema;
|
||||||
self.dialogData = data;
|
self.dialogData = data;
|
||||||
self.dialogOpen = true;
|
self.dialogOpen = true;
|
||||||
|
callback && dialogCallbacks.set(self.dialogData, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeDialog() {
|
function closeDialog(result?: any) {
|
||||||
|
const callback = dialogCallbacks.get(self.dialogData);
|
||||||
self.dialogOpen = false;
|
self.dialogOpen = false;
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
dialogCallbacks.delete(self.dialogData);
|
||||||
|
setTimeout(() => callback(result), 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
focus,
|
||||||
|
blur,
|
||||||
config,
|
config,
|
||||||
changeValue,
|
changeValue,
|
||||||
validate,
|
validate,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {types, getRoot, Instance} from 'mobx-state-tree';
|
||||||
import {extendObject, createObject} from '../utils/helper';
|
import {extendObject, createObject} from '../utils/helper';
|
||||||
import {IRendererStore} from './index';
|
import {IRendererStore} from './index';
|
||||||
import {dataMapping} from '../utils/tpl-builtin';
|
import {dataMapping} from '../utils/tpl-builtin';
|
||||||
|
import {SimpleMap} from '../utils/SimpleMap';
|
||||||
|
|
||||||
export const iRendererStore = types
|
export const iRendererStore = types
|
||||||
.model('iRendererStore', {
|
.model('iRendererStore', {
|
||||||
|
@ -32,7 +33,7 @@ export const iRendererStore = types
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.actions(self => {
|
.actions(self => {
|
||||||
const dialogCallbacks = new Map();
|
const dialogCallbacks = new SimpleMap<(result?: any) => void>();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
initData(data: object = {}) {
|
initData(data: object = {}) {
|
||||||
|
@ -97,10 +98,7 @@ export const iRendererStore = types
|
||||||
self.dialogData = data;
|
self.dialogData = data;
|
||||||
}
|
}
|
||||||
self.dialogOpen = true;
|
self.dialogOpen = true;
|
||||||
|
callback && dialogCallbacks.set(self.dialogData, callback);
|
||||||
if (callback) {
|
|
||||||
dialogCallbacks.set(self.dialogData, callback);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
closeDialog(result?: any) {
|
closeDialog(result?: any) {
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import find = require('lodash/find');
|
||||||
|
import findIndex = require('lodash/findIndex');
|
||||||
|
|
||||||
|
export class SimpleMap<V = any, K = any> {
|
||||||
|
private readonly list: Array<{
|
||||||
|
key: K;
|
||||||
|
value: V;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
set(key: K, value: V) {
|
||||||
|
this.list.push({
|
||||||
|
key,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key: K) {
|
||||||
|
const resolved = find(this.list, item => item.key === key);
|
||||||
|
return resolved ? resolved.value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(key: K) {
|
||||||
|
const idx = findIndex(this.list, item => item.key === key);
|
||||||
|
~idx && this.list.splice(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this.list.splice(0, this.list.length);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,13 +20,13 @@ interface ApiCacheConfig extends ApiObject {
|
||||||
|
|
||||||
const apiCaches: Array<ApiCacheConfig> = [];
|
const apiCaches: Array<ApiCacheConfig> = [];
|
||||||
|
|
||||||
export function normalizeApi(api: Api): ApiObject {
|
export function normalizeApi(api: Api, defaultMethod?: string): ApiObject {
|
||||||
if (typeof api === 'string') {
|
if (typeof api === 'string') {
|
||||||
let method = rSchema.test(api) ? RegExp.$1 : '';
|
let method = rSchema.test(api) ? RegExp.$1 : '';
|
||||||
method && (api = api.replace(method + ':', ''));
|
method && (api = api.replace(method + ':', ''));
|
||||||
|
|
||||||
api = {
|
api = {
|
||||||
method: method as any,
|
method: (method || defaultMethod) as any,
|
||||||
url: api
|
url: api
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
@ -46,7 +46,7 @@ export function buildApi(
|
||||||
[propName: string]: any;
|
[propName: string]: any;
|
||||||
} = {}
|
} = {}
|
||||||
): ApiObject {
|
): ApiObject {
|
||||||
api = normalizeApi(api);
|
api = normalizeApi(api, options.method);
|
||||||
const {autoAppend, ignoreData, ...rest} = options;
|
const {autoAppend, ignoreData, ...rest} = options;
|
||||||
|
|
||||||
api.config = {
|
api.config = {
|
||||||
|
|
|
@ -132,6 +132,12 @@ export function calculatePosition(
|
||||||
: getPosition(target, container);
|
: getPosition(target, container);
|
||||||
const {height: overlayHeight, width: overlayWidth} = getOffset(overlayNode);
|
const {height: overlayHeight, width: overlayWidth} = getOffset(overlayNode);
|
||||||
|
|
||||||
|
// auto 尝试四个方向对齐。
|
||||||
|
placement =
|
||||||
|
placement === 'auto'
|
||||||
|
? 'left-bottom-left-top right-bottom-right-top left-top-left-bottom right-top-right-bottom left-bottom-left-top'
|
||||||
|
: placement;
|
||||||
|
|
||||||
let positionLeft = 0,
|
let positionLeft = 0,
|
||||||
positionTop = 0,
|
positionTop = 0,
|
||||||
arrowOffsetLeft: any = '',
|
arrowOffsetLeft: any = '',
|
||||||
|
|
|
@ -373,6 +373,19 @@ export function isDisabled(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hasAbility(
|
||||||
|
schema: any,
|
||||||
|
ability: string,
|
||||||
|
data?: object,
|
||||||
|
defaultValue: boolean = true
|
||||||
|
): boolean {
|
||||||
|
return schema.hasOwnProperty(ability)
|
||||||
|
? schema[ability]
|
||||||
|
: schema.hasOwnProperty(`${ability}On`)
|
||||||
|
? evalExpression(schema[`${ability}On`], data || schema)
|
||||||
|
: defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
export function makeHorizontalDeeper(
|
export function makeHorizontalDeeper(
|
||||||
horizontal: {
|
horizontal: {
|
||||||
left: string;
|
left: string;
|
||||||
|
@ -691,6 +704,11 @@ export function mapTree<T extends TreeItem>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遍历树
|
||||||
|
* @param tree
|
||||||
|
* @param iterator
|
||||||
|
*/
|
||||||
export function eachTree<T extends TreeItem>(
|
export function eachTree<T extends TreeItem>(
|
||||||
tree: Array<T>,
|
tree: Array<T>,
|
||||||
iterator: (item: T, key: number, level: number) => any,
|
iterator: (item: T, key: number, level: number) => any,
|
||||||
|
@ -705,15 +723,19 @@ export function eachTree<T extends TreeItem>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在树中查找节点。
|
||||||
|
* @param tree
|
||||||
|
* @param iterator
|
||||||
|
*/
|
||||||
export function findTree<T extends TreeItem>(
|
export function findTree<T extends TreeItem>(
|
||||||
tree: Array<T>,
|
tree: Array<T>,
|
||||||
iterator: (item: T, key: number, level: number) => any,
|
iterator: (item: T, key: number, level: number, paths: Array<T>) => any
|
||||||
level: number = 1
|
|
||||||
): T | null {
|
): T | null {
|
||||||
let result: T | null = null;
|
let result: T | null = null;
|
||||||
|
|
||||||
everyTree(tree, (item, key, level) => {
|
everyTree(tree, (item, key, level, paths) => {
|
||||||
if (iterator(item, key, level)) {
|
if (iterator(item, key, level, paths)) {
|
||||||
result = item;
|
result = item;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -723,6 +745,64 @@ export function findTree<T extends TreeItem>(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在树中查找节点, 返回下标数组。
|
||||||
|
* @param tree
|
||||||
|
* @param iterator
|
||||||
|
*/
|
||||||
|
export function findTreeIndex<T extends TreeItem>(
|
||||||
|
tree: Array<T>,
|
||||||
|
iterator: (item: T, key: number, level: number, paths: Array<T>) => any
|
||||||
|
): Array<number> | undefined {
|
||||||
|
let idx: Array<number> = [];
|
||||||
|
|
||||||
|
findTree(tree, (item, index, level, paths) => {
|
||||||
|
if (iterator(item, index, level, paths)) {
|
||||||
|
idx = [index];
|
||||||
|
|
||||||
|
paths = paths.concat();
|
||||||
|
paths.unshift({
|
||||||
|
children: tree
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
for (let i = paths.length - 1; i > 0; i--) {
|
||||||
|
const prev = paths[i - 1];
|
||||||
|
const current = paths[i];
|
||||||
|
idx.unshift(prev.children!.indexOf(current));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return idx.length ? idx : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTree<T extends TreeItem>(
|
||||||
|
tree: Array<T>,
|
||||||
|
idx: Array<number> | number
|
||||||
|
): T | undefined | null {
|
||||||
|
const indexes = Array.isArray(idx) ? idx : [idx];
|
||||||
|
const lastIndex = indexes.pop()!;
|
||||||
|
let list: Array<T> | null = tree;
|
||||||
|
for (let i = 0, len = indexes.length; i < len; i++) {
|
||||||
|
const index = indexes[i];
|
||||||
|
if (!list![index]) {
|
||||||
|
list = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
list = list![index].children as any;
|
||||||
|
}
|
||||||
|
return list ? list[lastIndex] : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过滤树节点
|
||||||
|
*
|
||||||
|
* @param tree
|
||||||
|
* @param iterator
|
||||||
|
*/
|
||||||
export function filterTree<T extends TreeItem>(
|
export function filterTree<T extends TreeItem>(
|
||||||
tree: Array<T>,
|
tree: Array<T>,
|
||||||
iterator: (item: T, key: number, level: number) => boolean,
|
iterator: (item: T, key: number, level: number) => boolean,
|
||||||
|
@ -741,44 +821,111 @@ export function filterTree<T extends TreeItem>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断树中每个节点是否满足某个条件。
|
||||||
|
* @param tree
|
||||||
|
* @param iterator
|
||||||
|
*/
|
||||||
export function everyTree<T extends TreeItem>(
|
export function everyTree<T extends TreeItem>(
|
||||||
tree: Array<T>,
|
tree: Array<T>,
|
||||||
iterator: (item: T, key: number, level: number) => boolean,
|
iterator: (item: T, key: number, level: number, paths: Array<T>) => boolean,
|
||||||
level: number = 1
|
level: number = 1,
|
||||||
|
paths: Array<T> = []
|
||||||
): boolean {
|
): boolean {
|
||||||
return tree.every((item, index) => {
|
return tree.every((item, index) => {
|
||||||
const value: any = iterator(item, index, level);
|
const value: any = iterator(item, index, level, paths);
|
||||||
|
|
||||||
if (value && item.children && item.children.splice) {
|
if (value && item.children && item.children.splice) {
|
||||||
return everyTree(item.children, iterator, level + 1);
|
return everyTree(item.children, iterator, level + 1, paths.concat(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断树中是否有某些节点满足某个条件。
|
||||||
|
* @param tree
|
||||||
|
* @param iterator
|
||||||
|
*/
|
||||||
export function someTree<T extends TreeItem>(
|
export function someTree<T extends TreeItem>(
|
||||||
tree: Array<T>,
|
tree: Array<T>,
|
||||||
iterator: (item: T, key: number, level: number) => boolean,
|
iterator: (item: T, key: number, level: number, paths: Array<T>) => boolean
|
||||||
level: number = 1
|
|
||||||
): boolean {
|
): boolean {
|
||||||
return tree.some((item, index) => {
|
return !everyTree(tree, iterator);
|
||||||
const value: any = iterator(item, index, level);
|
|
||||||
|
|
||||||
if (!value && item.children && item.children.splice) {
|
|
||||||
return someTree(item.children, iterator, level + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flattenTree<T extends TreeItem>(tree: Array<T>): Array<T> {
|
/**
|
||||||
let flattened: Array<T> = [];
|
* 将树打平变成一维数组,可以传入第二个参数实现打平节点中的其他属性。
|
||||||
eachTree(tree, item => flattened.push(item));
|
*
|
||||||
|
* 比如:
|
||||||
|
*
|
||||||
|
* flattenTree([
|
||||||
|
* {
|
||||||
|
* id: 1,
|
||||||
|
* children: [
|
||||||
|
* { id: 2 },
|
||||||
|
* { id: 3 },
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
* ], item => item.id); // 输出位 [1, 2, 3]
|
||||||
|
*
|
||||||
|
* @param tree
|
||||||
|
* @param mapper
|
||||||
|
*/
|
||||||
|
export function flattenTree<T extends TreeItem>(tree: Array<T>): Array<T>;
|
||||||
|
export function flattenTree<T extends TreeItem, U>(
|
||||||
|
tree: Array<T>,
|
||||||
|
mapper: (value: T, index: number) => U
|
||||||
|
): Array<U>;
|
||||||
|
export function flattenTree<T extends TreeItem, U>(
|
||||||
|
tree: Array<T>,
|
||||||
|
mapper?: (value: T, index: number) => U
|
||||||
|
): Array<U> {
|
||||||
|
let flattened: Array<any> = [];
|
||||||
|
eachTree(tree, (item, index) =>
|
||||||
|
flattened.push(mapper ? mapper(item, index) : item)
|
||||||
|
);
|
||||||
return flattened;
|
return flattened;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作树,遵循 imutable, 每次返回一个新的树。
|
||||||
|
* 类似数组的 splice 不同的地方这个方法不修改原始数据,
|
||||||
|
* 同时第二个参数不是下标,而是下标数组,分别代表每一层的下标。
|
||||||
|
*
|
||||||
|
* 至于如何获取下标数组,请查看 findTreeIndex
|
||||||
|
*
|
||||||
|
* @param tree
|
||||||
|
* @param idx
|
||||||
|
* @param deleteCount
|
||||||
|
* @param ...items
|
||||||
|
*/
|
||||||
|
export function spliceTree<T extends TreeItem>(
|
||||||
|
tree: Array<T>,
|
||||||
|
idx: Array<number> | number,
|
||||||
|
deleteCount: number = 0,
|
||||||
|
...items: Array<T>
|
||||||
|
): Array<T> {
|
||||||
|
const list = tree.concat();
|
||||||
|
if (typeof idx === 'number') {
|
||||||
|
list.splice(idx, deleteCount, ...items);
|
||||||
|
} else if (Array.isArray(idx) && idx.length) {
|
||||||
|
const lastIdx = idx.pop()!;
|
||||||
|
let host = idx.reduce((list: Array<T>, idx) => {
|
||||||
|
const child = {
|
||||||
|
...list[idx],
|
||||||
|
children: list[idx].children ? list[idx].children!.concat() : []
|
||||||
|
};
|
||||||
|
list[idx] = child;
|
||||||
|
return child.children;
|
||||||
|
}, list);
|
||||||
|
host.splice(lastIdx, deleteCount, ...items);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
export function ucFirst(str?: string) {
|
export function ucFirst(str?: string) {
|
||||||
return str ? str.substring(0, 1).toUpperCase() + str.substring(1) : '';
|
return str ? str.substring(0, 1).toUpperCase() + str.substring(1) : '';
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue