mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-02 20:18:03 +08:00
commit
112c2b7e15
@ -35,7 +35,7 @@
|
|||||||
"downshift": "3.1.4",
|
"downshift": "3.1.4",
|
||||||
"echarts": "~4.1.0",
|
"echarts": "~4.1.0",
|
||||||
"flv.js": "1.5.0",
|
"flv.js": "1.5.0",
|
||||||
"froala-editor": "2.9.5",
|
"froala-editor": "2.9.6",
|
||||||
"history": "4.7.2",
|
"history": "4.7.2",
|
||||||
"hls.js": "0.12.2",
|
"hls.js": "0.12.2",
|
||||||
"hoist-non-react-statics": "3.3.0",
|
"hoist-non-react-statics": "3.3.0",
|
||||||
|
@ -35,10 +35,28 @@ $dark: $gray800 !default;
|
|||||||
$remFactor: 16px !default;
|
$remFactor: 16px !default;
|
||||||
|
|
||||||
// 字体相关
|
// 字体相关
|
||||||
$fontFamilySansSerif: -apple-system, BlinkMacSystemFont, 'SF Pro SC', 'SF Pro Text', 'Helvetica Neue', Helvetica,
|
$fontFamilySansSerif: -apple-system,
|
||||||
'PingFang SC', 'Segoe UI', Roboto, 'Hiragino Sans GB', 'Arial', 'microsoft yahei ui', 'Microsoft YaHei', SimSun,
|
BlinkMacSystemFont,
|
||||||
sans-serif !default;
|
'SF Pro SC',
|
||||||
$fontFamilyMonospace: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !default;
|
'SF Pro Text',
|
||||||
|
'Helvetica Neue',
|
||||||
|
Helvetica,
|
||||||
|
'PingFang SC',
|
||||||
|
'Segoe UI',
|
||||||
|
Roboto,
|
||||||
|
'Hiragino Sans GB',
|
||||||
|
'Arial',
|
||||||
|
'microsoft yahei ui',
|
||||||
|
'Microsoft YaHei',
|
||||||
|
SimSun,
|
||||||
|
sans-serif !default;
|
||||||
|
$fontFamilyMonospace: SFMono-Regular,
|
||||||
|
Menlo,
|
||||||
|
Monaco,
|
||||||
|
Consolas,
|
||||||
|
'Liberation Mono',
|
||||||
|
'Courier New',
|
||||||
|
monospace !default;
|
||||||
$fontFamilyBase: $fontFamilySansSerif !default;
|
$fontFamilyBase: $fontFamilySansSerif !default;
|
||||||
|
|
||||||
$fontSizeBase: px2rem(14px) !default; // Assumes the browser default, typically `16px`
|
$fontSizeBase: px2rem(14px) !default; // Assumes the browser default, typically `16px`
|
||||||
@ -81,13 +99,11 @@ $boxShadow: 0 0.5rem 1rem rgba($black, 0.15) !default;
|
|||||||
$boxShadowLg: 0 1rem 3rem rgba($black, 0.175) !default;
|
$boxShadowLg: 0 1rem 3rem rgba($black, 0.175) !default;
|
||||||
|
|
||||||
// 窗口适配
|
// 窗口适配
|
||||||
$breakpoints: (
|
$breakpoints: (xs: 0,
|
||||||
xs: 0,
|
|
||||||
sm: 576px,
|
sm: 576px,
|
||||||
md: 768px,
|
md: 768px,
|
||||||
lg: 992px,
|
lg: 992px,
|
||||||
xl: 1200px
|
xl: 1200px) !default;
|
||||||
) !default;
|
|
||||||
|
|
||||||
// 段落间距
|
// 段落间距
|
||||||
$paragraph-marginBottom: 1rem !default;
|
$paragraph-marginBottom: 1rem !default;
|
||||||
@ -166,7 +182,8 @@ $Layout-brand-color: lighten($Layout-brandBar-color, 25%) !default;
|
|||||||
$Layout-header-height: px2rem(50px) !default;
|
$Layout-header-height: px2rem(50px) !default;
|
||||||
$Layout-headerBar-borderBottom: none !default;
|
$Layout-headerBar-borderBottom: none !default;
|
||||||
$Layout-header-bg: $white !default;
|
$Layout-header-bg: $white !default;
|
||||||
$Layout-header-boxShadow: 0 px2rem(2px) px2rem(2px) rgba(0, 0, 0, 0.05), 0 1px 0 rgba(0, 0, 0, 0.05) !default;
|
$Layout-header-boxShadow: 0 px2rem(2px) px2rem(2px) rgba(0, 0, 0, 0.05),
|
||||||
|
0 1px 0 rgba(0, 0, 0, 0.05) !default;
|
||||||
$Layout-nav-height: px2rem(40px) !default;
|
$Layout-nav-height: px2rem(40px) !default;
|
||||||
$Layout-nav-lgHeight: px2rem(50px) !default;
|
$Layout-nav-lgHeight: px2rem(50px) !default;
|
||||||
$Layout-nav--folded-height: px2rem(50px) !default;
|
$Layout-nav--folded-height: px2rem(50px) !default;
|
||||||
@ -376,7 +393,8 @@ $Tabs--line-borderWidth: px2rem(2px) !default;
|
|||||||
$Tabs--line-linkPadding: 10px 0 !default;
|
$Tabs--line-linkPadding: 10px 0 !default;
|
||||||
$Tabs--line-linkMargin: 0 40px 0 0 !default;
|
$Tabs--line-linkMargin: 0 40px 0 0 !default;
|
||||||
$Tabs--line-content-bg: transparent !default;
|
$Tabs--line-content-bg: transparent !default;
|
||||||
$Tabs--line-content-padding: 20px 0 !default;;
|
$Tabs--line-content-padding: 20px 0 !default;
|
||||||
|
;
|
||||||
|
|
||||||
$Tabs--card-padding: px2rem(6px) 0 0 px2rem(10px);
|
$Tabs--card-padding: px2rem(6px) 0 0 px2rem(10px);
|
||||||
$Tabs--card-bg: darken($body-bg, 5%) !default;
|
$Tabs--card-bg: darken($body-bg, 5%) !default;
|
||||||
@ -737,7 +755,8 @@ $Button--lg-lineHeight: 24 / 20 !default;
|
|||||||
$Button--lg-paddingX: px2rem(16px) !default;
|
$Button--lg-paddingX: px2rem(16px) !default;
|
||||||
$Button--lg-paddingY: ($Button--lg-height - $Button-borderWidth * 2 - $Button--lg-lineHeight * $Button--lg-fontSize)/2 !default;
|
$Button--lg-paddingY: ($Button--lg-height - $Button-borderWidth * 2 - $Button--lg-lineHeight * $Button--lg-fontSize)/2 !default;
|
||||||
|
|
||||||
$Button-boxShadow: inset 0 1px 0 rgba($white, 0.15), 0 1px 1px rgba($black, 0.075) !default;
|
$Button-boxShadow: inset 0 1px 0 rgba($white, 0.15),
|
||||||
|
0 1px 1px rgba($black, 0.075) !default;
|
||||||
$Button-onFocus-boxShadow: none !default;
|
$Button-onFocus-boxShadow: none !default;
|
||||||
$Button-onActive-boxShadow: inset 0 3px 5px rgba($black, 0.125) !default;
|
$Button-onActive-boxShadow: inset 0 3px 5px rgba($black, 0.125) !default;
|
||||||
$Button-onDisabled-opacity: 0.65 !default;
|
$Button-onDisabled-opacity: 0.65 !default;
|
||||||
@ -749,8 +768,10 @@ $Button-borderRadius: $borderRadius !default;
|
|||||||
$Button--lg-borderRadius: $borderRadius !default;
|
$Button--lg-borderRadius: $borderRadius !default;
|
||||||
$Button--sm-borderRadius: $borderRadius !default;
|
$Button--sm-borderRadius: $borderRadius !default;
|
||||||
|
|
||||||
$Button-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
|
$Button-transition: color 0.15s ease-in-out,
|
||||||
box-shadow 0.15s ease-in-out !default;
|
background-color 0.15s ease-in-out,
|
||||||
|
border-color 0.15s ease-in-out,
|
||||||
|
box-shadow 0.15s ease-in-out !default;
|
||||||
|
|
||||||
$Button--primary-bg: $primary !default;
|
$Button--primary-bg: $primary !default;
|
||||||
$Button--primary-border: $Button--primary-bg !default;
|
$Button--primary-border: $Button--primary-bg !default;
|
||||||
@ -908,8 +929,7 @@ $ColorPicker-height: $Form-input-height !default;
|
|||||||
$ColorPicker-lineHeight: $Form-input-lineHeight !default;
|
$ColorPicker-lineHeight: $Form-input-lineHeight !default;
|
||||||
$ColorPicker-fontSize: $Form-input-fontSize !default;
|
$ColorPicker-fontSize: $Form-input-fontSize !default;
|
||||||
$ColorPicker-paddingX: px2rem(12px) !default;
|
$ColorPicker-paddingX: px2rem(12px) !default;
|
||||||
$ColorPicker-paddingY: ($ColorPicker-height - $ColorPicker-lineHeight * $ColorPicker-fontSize)/2 -
|
$ColorPicker-paddingY: ($ColorPicker-height - $ColorPicker-lineHeight * $ColorPicker-fontSize)/2 - $ColorPicker-borderWidth !default;
|
||||||
$ColorPicker-borderWidth !default;
|
|
||||||
$ColorPicker-placeholderColor: $Form-input-placeholderColor !default;
|
$ColorPicker-placeholderColor: $Form-input-placeholderColor !default;
|
||||||
$ColorPicker-onFocused-borderColor: $Form-input-onFocused-borderColor !default;
|
$ColorPicker-onFocused-borderColor: $Form-input-onFocused-borderColor !default;
|
||||||
$DatePicker-onHover-borderColor: $Form-input-borderColor !default;
|
$DatePicker-onHover-borderColor: $Form-input-borderColor !default;
|
||||||
@ -1039,9 +1059,7 @@ $Combo-addBtn-borderRadius: $Button-borderRadius;
|
|||||||
$Combo-addBtn-height: px2rem(26px) !default;
|
$Combo-addBtn-height: px2rem(26px) !default;
|
||||||
$Combo-addBtn-lineHeight: $Button--sm-lineHeight !default;
|
$Combo-addBtn-lineHeight: $Button--sm-lineHeight !default;
|
||||||
$Combo-addBtn-paddingX: $Button--sm-paddingX !default;
|
$Combo-addBtn-paddingX: $Button--sm-paddingX !default;
|
||||||
$Combo-addBtn-paddingY: (
|
$Combo-addBtn-paddingY: ($Combo-addBtn-height - $Button-borderWidth * 2 - $Combo-addBtn-lineHeight * $Combo-addBtn-fontSize)/2 !default;
|
||||||
$Combo-addBtn-height - $Button-borderWidth * 2 - $Combo-addBtn-lineHeight * $Combo-addBtn-fontSize
|
|
||||||
)/2 !default;
|
|
||||||
|
|
||||||
$Combo--vertical-item-gap: px2rem(5px);
|
$Combo--vertical-item-gap: px2rem(5px);
|
||||||
$Combo--vertical-item-borderColor: $borderColor !default;
|
$Combo--vertical-item-borderColor: $borderColor !default;
|
||||||
@ -1080,9 +1098,7 @@ $SubForm--addBtn-borderRadius: $Button-borderRadius;
|
|||||||
$SubForm--addBtn-height: $Button--sm-height !default;
|
$SubForm--addBtn-height: $Button--sm-height !default;
|
||||||
$SubForm--addBtn-lineHeight: $Button--sm-lineHeight !default;
|
$SubForm--addBtn-lineHeight: $Button--sm-lineHeight !default;
|
||||||
$SubForm--addBtn-paddingX: $Button--sm-paddingX !default;
|
$SubForm--addBtn-paddingX: $Button--sm-paddingX !default;
|
||||||
$SubForm--addBtn-paddingY: (
|
$SubForm--addBtn-paddingY: ($SubForm--addBtn-height - $Button-borderWidth * 2 - $SubForm--addBtn-lineHeight * $SubForm--addBtn-fontSize)/2 !default;
|
||||||
$SubForm--addBtn-height - $Button-borderWidth * 2 - $SubForm--addBtn-lineHeight * $SubForm--addBtn-fontSize
|
|
||||||
)/2 !default;
|
|
||||||
|
|
||||||
// InputRange
|
// InputRange
|
||||||
$InputRange-fontFamily: $fontFamilyBase !default;
|
$InputRange-fontFamily: $fontFamilyBase !default;
|
||||||
@ -1095,11 +1111,11 @@ $InputRange-onDisabled-color: #cccccc !default;
|
|||||||
$InputRange-slider-bg: $InputRange-primaryColor !default;
|
$InputRange-slider-bg: $InputRange-primaryColor !default;
|
||||||
$InputRange-slider-border: px2rem(1px) solid $InputRange-primaryColor !default;
|
$InputRange-slider-border: px2rem(1px) solid $InputRange-primaryColor !default;
|
||||||
$InputRange-slider-onFocus-borderRadius: $borderRadiusMd !default;
|
$InputRange-slider-onFocus-borderRadius: $borderRadiusMd !default;
|
||||||
$InputRange-slider-onFocus-boxShadow: 0 0 0 $InputRange-slider-onFocus-borderRadius
|
$InputRange-slider-onFocus-boxShadow: 0 0 0 $InputRange-slider-onFocus-borderRadius transparentize($InputRange-slider-bg, 0.8) !default;
|
||||||
transparentize($InputRange-slider-bg, 0.8) !default;
|
|
||||||
$InputRange-slider-height: px2rem(24px) !default;
|
$InputRange-slider-height: px2rem(24px) !default;
|
||||||
$InputRange-slider-width: px2rem(18px) !default;
|
$InputRange-slider-width: px2rem(18px) !default;
|
||||||
$InputRange-slider-transition: transform 0.3s ease-out, box-shadow 0.3s ease-out !default;
|
$InputRange-slider-transition: transform 0.3s ease-out,
|
||||||
|
box-shadow 0.3s ease-out !default;
|
||||||
$InputRange-sliderContainer-transition: left 0.3s ease-out !default;
|
$InputRange-sliderContainer-transition: left 0.3s ease-out !default;
|
||||||
$InputRange-slider-onActive-transform: scale(1.3) !default;
|
$InputRange-slider-onActive-transform: scale(1.3) !default;
|
||||||
$InputRange-slider-onDisabled-bg: $InputRange-onDisabled-color !default;
|
$InputRange-slider-onDisabled-bg: $InputRange-onDisabled-color !default;
|
||||||
@ -1115,10 +1131,26 @@ $InputRange-label--value-positionTop: px2rem(-40px) !default;
|
|||||||
// input-range-track
|
// input-range-track
|
||||||
$InputRange-track-bg: $InputRange-neutralLightColor !default;
|
$InputRange-track-bg: $InputRange-neutralLightColor !default;
|
||||||
$InputRange-track-height: px2rem(12px) !default;
|
$InputRange-track-height: px2rem(12px) !default;
|
||||||
$InputRange-track-transition: left 0.3s ease-out, width 0.3s ease-out !default;
|
$InputRange-track-transition: left 0.3s ease-out,
|
||||||
|
width 0.3s ease-out !default;
|
||||||
$InputRange-track-onActive-bg: $InputRange-primaryColor !default;
|
$InputRange-track-onActive-bg: $InputRange-primaryColor !default;
|
||||||
$InputRange-track-onDisabled-bg: $InputRange-neutralLightColor !default;
|
$InputRange-track-onDisabled-bg: $InputRange-neutralLightColor !default;
|
||||||
|
|
||||||
|
// ImageControl
|
||||||
|
$ImageControl-addBtn-bg: $Button--default-bg !default;
|
||||||
|
$ImageControl-addBtn-border: $Button--default-border !default;
|
||||||
|
$ImageControl-addBtn-color: $Button--default-color !default;
|
||||||
|
$ImageControl-addBtn-onHover-bg: darken($ImageControl-addBtn-bg, 7.5%) !default;
|
||||||
|
$ImageControl-addBtn-onHover-border: darken($ImageControl-addBtn-border, 10%) !default;
|
||||||
|
$ImageControl-addBtn-onHover-color: $Button--default-color !default;
|
||||||
|
$ImageControl-addBtn-onActive-bg: darken($ImageControl-addBtn-bg, 10%) !default;
|
||||||
|
$ImageControl-addBtn-onActive-border: darken($ImageControl-addBtn-border, 12.5%) !default;
|
||||||
|
$ImageControl-addBtn-onActive-color: $ImageControl-addBtn-color !default;
|
||||||
|
$ImageControl-addBtn-onDisabled-bg: $Form-input-onDisabled-bg !default;
|
||||||
|
$ImageControl-addBtn-onDisabled-border: $Form-input-onDisabled-borderColor !default;
|
||||||
|
$ImageControl-addBtn-onDisabled-color: $text--muted-color !default;
|
||||||
|
|
||||||
|
|
||||||
// Tag
|
// Tag
|
||||||
$TagControl-sugTip-color: $info !default;
|
$TagControl-sugTip-color: $info !default;
|
||||||
|
|
||||||
@ -1138,10 +1170,7 @@ $TagControl-sugBtn-borderRadius: $Button-borderRadius !default;
|
|||||||
$TagControl-sugBtn-height: $Button--sm-height !default;
|
$TagControl-sugBtn-height: $Button--sm-height !default;
|
||||||
$TagControl-sugBtn-lineHeight: $Button--sm-lineHeight !default;
|
$TagControl-sugBtn-lineHeight: $Button--sm-lineHeight !default;
|
||||||
$TagControl-sugBtn-paddingX: $Button--sm-paddingX !default;
|
$TagControl-sugBtn-paddingX: $Button--sm-paddingX !default;
|
||||||
$TagControl-sugBtn-paddingY: (
|
$TagControl-sugBtn-paddingY: ($TagControl-sugBtn-height - $Button-borderWidth * 2 - $TagControl-sugBtn-lineHeight * $TagControl-sugBtn-fontSize)/2 !default;
|
||||||
$TagControl-sugBtn-height - $Button-borderWidth * 2 - $TagControl-sugBtn-lineHeight *
|
|
||||||
$TagControl-sugBtn-fontSize
|
|
||||||
)/2 !default;
|
|
||||||
|
|
||||||
// Wizard
|
// Wizard
|
||||||
$Wizard-steps-bg: $gray100 !default;
|
$Wizard-steps-bg: $gray100 !default;
|
||||||
@ -1312,4 +1341,4 @@ $Picker-iconColor: $gray600 !default;
|
|||||||
$Picker-onHover-iconColor: darken($Picker-iconColor, 10%) !default;
|
$Picker-onHover-iconColor: darken($Picker-iconColor, 10%) !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;
|
@ -1,189 +1,187 @@
|
|||||||
// todo
|
|
||||||
|
|
||||||
.#{$ns}ImageControl {
|
.#{$ns}ImageControl {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
|
||||||
|
|
||||||
.drop-zone {
|
&-addBtn {
|
||||||
border: $Form-input-borderWidth * 2 dashed $Form-input-borderColor;
|
margin: 0;
|
||||||
height: 70px;
|
width: px2rem(120px);
|
||||||
text-align: center;
|
height: px2rem(120px);
|
||||||
display: table;
|
display: inline-flex;
|
||||||
width: 100%;
|
justify-content: center;
|
||||||
color: $text--muted-color;
|
align-items: center;
|
||||||
position: relative;
|
border: $borderWidth solid $borderColor;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
> div:not(.image-list) {
|
@include button-variant($ImageControl-addBtn-bg,
|
||||||
display: table-cell;
|
$ImageControl-addBtn-border,
|
||||||
vertical-align: middle;
|
$ImageControl-addBtn-color,
|
||||||
|
$ImageControl-addBtn-onHover-bg,
|
||||||
|
$ImageControl-addBtn-onHover-border,
|
||||||
|
$ImageControl-addBtn-onHover-color,
|
||||||
|
$ImageControl-addBtn-onActive-bg,
|
||||||
|
$ImageControl-addBtn-onActive-border,
|
||||||
|
$ImageControl-addBtn-onActive-color);
|
||||||
|
|
||||||
|
>svg {
|
||||||
|
width: px2rem(50px);
|
||||||
|
height: px2rem(50px);
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
border: px2rem(1px) solid $ImageControl-addBtn-onDisabled-border;
|
||||||
|
background: $ImageControl-addBtn-onDisabled-bg;
|
||||||
|
color: $ImageControl-addBtn-onDisabled-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-files {
|
&-dropzone.is-active &-addBtn {
|
||||||
cursor: default;
|
border-color: $ImageControl-addBtn-onHover-border;
|
||||||
border: 2px dashed transparent;
|
background: $ImageControl-addBtn-onHover-bg;
|
||||||
|
color: $ImageControl-addBtn-onHover-color;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.drop-zone-wrapper:focus .drop-zone:not(.disabled) {
|
&-item {
|
||||||
color: $text-color;
|
border: $borderWidth solid $borderColor;
|
||||||
border: $Form-input-borderWidth * 2 dashed $Form-input-onFocused-borderColor;
|
vertical-align: top;
|
||||||
height: 90px;
|
padding: px2rem(5px);
|
||||||
padding-bottom: 20px;
|
display: inline-block;
|
||||||
margin-bottom: 10px;
|
margin-right: px2rem(15px);
|
||||||
|
margin-right: px2rem(15px);
|
||||||
&:after {
|
|
||||||
content: "温馨提示:当前状态可以粘贴剪切板中的文件。如截屏";
|
|
||||||
position: absolute;
|
|
||||||
left: 10px;
|
|
||||||
bottom: 3px;
|
|
||||||
color: $text--muted-color;
|
|
||||||
font-size: $fontSizeSm;
|
|
||||||
overflow: hidden;
|
|
||||||
right: 0;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.drop-zone.disabled {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drop-zone-active,
|
|
||||||
.drop-zone-active.has-files,
|
|
||||||
.drop-zone:hover:not(.disabled):not(.has-files) {
|
|
||||||
color: $text-color;
|
|
||||||
border: $Form-input-borderWidth * 2 dashed $Form-input-onFocused-borderColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-list {
|
|
||||||
outline: none;
|
|
||||||
margin: -5px;
|
|
||||||
|
|
||||||
.image-item {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 5px;
|
}
|
||||||
width: 90px;
|
|
||||||
|
|
||||||
.img-wrapper {
|
&-itemImageWrap {
|
||||||
width: 90px;
|
width: px2rem(108px);
|
||||||
min-height: 50px;
|
height: px2rem(108px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
position: relative;
|
||||||
|
|
||||||
img {
|
>img {
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-spinner {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
margin: -15px 0 0 -20px;
|
height: 100%;
|
||||||
color: #fff;
|
width: auto;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.image-overlay {
|
&-itemOverlay {
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.6);
|
||||||
border-radius: 5px;
|
position: absolute;
|
||||||
position: absolute;
|
width: px2rem(108px);
|
||||||
top: 0;
|
height: px2rem(108px);
|
||||||
left: 0;
|
display: none;
|
||||||
right: 0;
|
top: px2rem(5px);
|
||||||
bottom: 0;
|
left: px2rem(5px);
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-content: center;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
> a,
|
justify-content: center;
|
||||||
> button {
|
align-items: center;
|
||||||
flex: 1;
|
align-content: center;
|
||||||
outline: none;
|
flex-wrap: wrap;
|
||||||
display: flex;
|
color: #fff;
|
||||||
color: #fff;
|
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
|
||||||
text-decoration: none;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
opacity: 0.6;
|
|
||||||
|
|
||||||
&:hover {
|
>div {
|
||||||
opacity: 1;
|
width: 100%;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
>a {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 5px;
|
||||||
|
line-height: 1;
|
||||||
|
font-size: px2rem(16px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.image-list-multiple .image-item .img-wrapper {
|
&-item:hover &-itemOverlay {
|
||||||
height: 90px;
|
display: flex;
|
||||||
display: table-cell;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-add-btn {
|
&-itemClear {
|
||||||
width: 90px;
|
position: absolute;
|
||||||
height: 110px;
|
|
||||||
line-height: 110px;
|
|
||||||
margin: 5px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
float: left;
|
|
||||||
|
|
||||||
&:hover .fa {
|
color: #999;
|
||||||
color: $dark;
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
>svg {
|
||||||
|
top: 0;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-item.uploaded {
|
&-itemInfo {
|
||||||
.fa-spinner {
|
display: inline-flex;
|
||||||
display: none;
|
width: 110px;
|
||||||
}
|
height: 110px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
.image-overlay {
|
>p {
|
||||||
display: none;
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-progress {
|
||||||
|
width: 70px;
|
||||||
|
height: 5px;
|
||||||
|
background: #ebebeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-progressValue {
|
||||||
|
height: 5px;
|
||||||
|
display: block;
|
||||||
|
background: $info;
|
||||||
|
min-width: 10%;
|
||||||
|
transition: ease-out width 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-retryBtn {
|
||||||
|
margin: 0;
|
||||||
|
width: px2rem(108px);
|
||||||
|
height: px2rem(108px);
|
||||||
|
display: inline-flex;
|
||||||
|
cursor: pointer;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.image-overlay {
|
color: #333;
|
||||||
display: flex;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
>p {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
color: $danger;
|
||||||
|
margin: 10px 0 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.cropper-wrapper {
|
&-errorMsg {
|
||||||
position: relative;
|
color: $danger;
|
||||||
|
margin: 5px 0 0;
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 400px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
&-uploadBtn {
|
||||||
position: absolute;
|
margin-top: 5px;
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.btn:nth-child(2n + 1) {
|
|
||||||
bottom: 40px;
|
|
||||||
right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.amis-image-control.form-contorl-inline,
|
|
||||||
.form-group-inline .amis-image-control {
|
|
||||||
display: inline-block;
|
|
||||||
min-width: 280px;
|
|
||||||
}
|
|
||||||
}
|
|
@ -98,8 +98,7 @@ $Form-select-caret-onHover-iconColor: $primary;
|
|||||||
$Form-select-caret-fontSize: px2rem(12px);
|
$Form-select-caret-fontSize: px2rem(12px);
|
||||||
$Form-select-outer-borderWidth: 0;
|
$Form-select-outer-borderWidth: 0;
|
||||||
$Form-select-outer-top: px2rem(32px);
|
$Form-select-outer-top: px2rem(32px);
|
||||||
$Form-select-outer-boxShadow: px2rem(2px) px2rem(4px) px2rem(8px)
|
$Form-select-outer-boxShadow: px2rem(2px) px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0.2);
|
||||||
rgba(0, 0, 0, 0.2);
|
|
||||||
$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;
|
||||||
@ -288,6 +287,20 @@ $ListControl-item-onDisabled-bg: #f5f5f5;
|
|||||||
$ListControl-item-onDisabled-color: #999;
|
$ListControl-item-onDisabled-color: #999;
|
||||||
$ListControl-item-onDisabled-borderColor: #ebebeb;
|
$ListControl-item-onDisabled-borderColor: #ebebeb;
|
||||||
|
|
||||||
|
// 图片上传
|
||||||
|
$ImageControl-addBtn-bg: #fff;
|
||||||
|
$ImageControl-addBtn-border: px2rem(1px) solid #dbdbdb;
|
||||||
|
$ImageControl-addBtn-color: #666;
|
||||||
|
$ImageControl-addBtn-onHover-bg: $ImageControl-addBtn-bg;
|
||||||
|
$ImageControl-addBtn-onHover-border: $primary;
|
||||||
|
$ImageControl-addBtn-onHover-color: $primary;
|
||||||
|
$ImageControl-addBtn-onActive-bg: #f3f9fe;
|
||||||
|
$ImageControl-addBtn-onActive-border: $primary;
|
||||||
|
$ImageControl-addBtn-onActive-color: $primary;
|
||||||
|
$ImageControl-addBtn-onDisabled-bg: #f5f5f5;
|
||||||
|
$ImageControl-addBtn-onDisabled-border: #ebebeb;
|
||||||
|
$ImageControl-addBtn-onDisabled-color: #ccc;
|
||||||
|
|
||||||
// Modal
|
// Modal
|
||||||
$Modal-overlay-bg: rgba(0, 0, 0, 0.7);
|
$Modal-overlay-bg: rgba(0, 0, 0, 0.7);
|
||||||
$Modal-content-borderWidth: 0;
|
$Modal-content-borderWidth: 0;
|
||||||
@ -350,7 +363,7 @@ $Pagination-onActive-border: px2rem(1px) solid $primary;
|
|||||||
|
|
||||||
// Panel
|
// Panel
|
||||||
$Panel-borderRadius: 0;
|
$Panel-borderRadius: 0;
|
||||||
$Panel-fixedBottom-boxShadow: 0 -2px 10px 0 rgba(0,0,0,0.05);
|
$Panel-fixedBottom-boxShadow: 0 -2px 10px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
// Nav
|
// Nav
|
||||||
$Nav-item-fontSize: px2rem(16px);
|
$Nav-item-fontSize: px2rem(16px);
|
||||||
@ -538,4 +551,4 @@ $Card-actions-onChecked-onHover-bg: $white;
|
|||||||
@import "../components/form/nested-select";
|
@import "../components/form/nested-select";
|
||||||
@import "../components/form/icon-picker";
|
@import "../components/form/icon-picker";
|
||||||
|
|
||||||
@import "../utilities";
|
@import "../utilities";
|
@ -10,7 +10,7 @@ import hoistNonReactStatic = require('hoist-non-react-statics');
|
|||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import {dataMapping} from './utils/tpl-builtin';
|
import {dataMapping} from './utils/tpl-builtin';
|
||||||
import {RendererEnv, RendererProps} from './factory';
|
import {RendererEnv, RendererProps} from './factory';
|
||||||
import {noop, autobind} from './utils/helper';
|
import {noop, autobind, qsstringify} from './utils/helper';
|
||||||
import {RendererData, Action} from './types';
|
import {RendererData, Action} from './types';
|
||||||
|
|
||||||
interface ScopedComponentType extends React.Component<RendererProps> {
|
interface ScopedComponentType extends React.Component<RendererProps> {
|
||||||
@ -139,7 +139,7 @@ function createScopedTools(path?: string, parent?: AlisIScopedContext, env?: Ren
|
|||||||
...(location.search ? qs.parse(location.search.substring(1)) : {}),
|
...(location.search ? qs.parse(location.search.substring(1)) : {}),
|
||||||
...values
|
...values
|
||||||
};
|
};
|
||||||
const link = location.pathname + '?' + qs.stringify(query);
|
const link = location.pathname + '?' + qsstringify(query);
|
||||||
env.updateLocation(link);
|
env.updateLocation(link);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -625,7 +625,7 @@ 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 ? (
|
{searchable && !disabled ? (
|
||||||
<input
|
<input
|
||||||
{...getInputProps({
|
{...getInputProps({
|
||||||
className: cx(`Select-input`),
|
className: cx(`Select-input`),
|
||||||
|
@ -33,6 +33,12 @@ import PlusIcon from '../icons/plus.svg';
|
|||||||
import MinusIcon from '../icons/minus.svg';
|
import MinusIcon from '../icons/minus.svg';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import PencilIcon from '../icons/pencil.svg';
|
import PencilIcon from '../icons/pencil.svg';
|
||||||
|
// @ts-ignore
|
||||||
|
import ViewIcon from '../icons/view.svg';
|
||||||
|
// @ts-ignore
|
||||||
|
import RemoveIcon from '../icons/remove.svg';
|
||||||
|
// @ts-ignore
|
||||||
|
import RetryIcon from '../icons/retry.svg';
|
||||||
|
|
||||||
// 兼容原来的用法,后续不直接试用。
|
// 兼容原来的用法,后续不直接试用。
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -82,6 +88,9 @@ registerIcon('check', CheckIcon);
|
|||||||
registerIcon('plus', PlusIcon);
|
registerIcon('plus', PlusIcon);
|
||||||
registerIcon('minus', MinusIcon);
|
registerIcon('minus', MinusIcon);
|
||||||
registerIcon('pencil', PencilIcon);
|
registerIcon('pencil', PencilIcon);
|
||||||
|
registerIcon('view', ViewIcon);
|
||||||
|
registerIcon('remove', RemoveIcon);
|
||||||
|
registerIcon('retry', RetryIcon);
|
||||||
|
|
||||||
export function Icon({
|
export function Icon({
|
||||||
icon,
|
icon,
|
||||||
|
9
src/icons/remove.svg
Normal file
9
src/icons/remove.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 12 12" version="1.1" p-id="1463">
|
||||||
|
<g>
|
||||||
|
<rect id="Rectangle-path" x="4" y="5" width="1" height="4"></rect>
|
||||||
|
<rect id="Rectangle-path" x="7" y="5" width="1" height="4"></rect>
|
||||||
|
<path d="M0,2 L0,3 L1,3 L1,11 L1,11.5 L1,12 L11,12 L11,11.5 L11,11 L11,3 L12,3 L12,2 L0,2 Z M10,11 L2,11 L2,3 L10,3 L10,11 Z" id="Shape"></path>
|
||||||
|
<rect id="Rectangle-path" x="4" y="0" width="4" height="1"></rect>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 537 B |
4
src/icons/retry.svg
Normal file
4
src/icons/retry.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg viewBox="0 0 1024 1024" version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M972.8 102.4c-30.72 0-51.2 20.48-51.2 51.2v51.2c-51.2-71.68-122.88-128-204.8-158.72C460.8-66.56 158.72 51.2 46.08 307.2S51.2 865.28 307.2 977.92 865.28 972.8 977.92 716.8H972.8c0-30.72-20.48-51.2-51.2-51.2s-51.2 20.48-51.2 51.2h-5.12c-46.08 76.8-112.64 138.24-199.68 174.08-209.92 87.04-445.44-15.36-532.48-225.28S148.48 215.04 358.4 133.12c189.44-81.92 404.48 0 506.88 174.08H768c-30.72 0-51.2 20.48-51.2 51.2s20.48 51.2 51.2 51.2h204.8c30.72 0 51.2-20.48 51.2-51.2V153.6c0-30.72-20.48-51.2-51.2-51.2z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 615 B |
7
src/icons/view.svg
Normal file
7
src/icons/view.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 10" version="1.1" p-id="1463">
|
||||||
|
<g>
|
||||||
|
<path d="M8,1 C11,1 13.7,3.8 14.7,5 C13.7,6.2 11,9 8,9 C5,9 2.3,6.2 1.3,5 C2.3,3.8 5,1 8,1 L8,1 Z M8,0 C3.6,0 0,5 0,5 C0,5 3.6,10 8,10 C12.4,10 16,5 16,5 C16,5 12.4,0 8,0 L8,0 Z"></path>
|
||||||
|
<path d="M8,2 C9.7,2 11,3.3 11,5 C11,6.7 9.7,8 8,8 C6.3,8 5,6.7 5,5 C5,3.3 6.3,2 8,2 L8,2 Z M8,1 C5.8,1 4,2.8 4,5 C4,7.2 5.8,9 8,9 C10.2,9 12,7.2 12,5 C12,2.8 10.2,1 8,1 L8,1 Z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 547 B |
@ -10,7 +10,8 @@ import {
|
|||||||
isObjectShallowModified,
|
isObjectShallowModified,
|
||||||
noop,
|
noop,
|
||||||
isVisible,
|
isVisible,
|
||||||
getVariable
|
getVariable,
|
||||||
|
qsstringify
|
||||||
} from '../utils/helper';
|
} from '../utils/helper';
|
||||||
import {observer} from 'mobx-react';
|
import {observer} from 'mobx-react';
|
||||||
import partition = require('lodash/partition');
|
import partition = require('lodash/partition');
|
||||||
@ -167,12 +168,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
|||||||
this.mounted = true;
|
this.mounted = true;
|
||||||
|
|
||||||
if (syncLocation && location && (location.query || location.search)) {
|
if (syncLocation && location && (location.query || location.search)) {
|
||||||
store.updateQuery(
|
store.updateQuery(qs.parse(location.search.substring(1)), undefined, pageField, perPageField);
|
||||||
qs.parse(location.search.substring(1)),
|
|
||||||
undefined,
|
|
||||||
pageField,
|
|
||||||
perPageField
|
|
||||||
);
|
|
||||||
} else if (syncLocation && !location && window.location.search) {
|
} else if (syncLocation && !location && window.location.search) {
|
||||||
store.updateQuery(
|
store.updateQuery(
|
||||||
qs.parse(window.location.search.substring(1)) as object,
|
qs.parse(window.location.search.substring(1)) as object,
|
||||||
@ -413,7 +409,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
|||||||
search: boolean = true
|
search: boolean = true
|
||||||
) {
|
) {
|
||||||
const {store, syncLocation, env, pageField, perPageField} = this.props;
|
const {store, syncLocation, env, pageField, perPageField} = this.props;
|
||||||
values = syncLocation ? qs.parse(qs.stringify(values)) : values;
|
values = syncLocation ? qs.parse(qsstringify(values)) : values;
|
||||||
|
|
||||||
store.updateQuery(
|
store.updateQuery(
|
||||||
{
|
{
|
||||||
@ -577,35 +573,35 @@ export default class CRUD extends React.Component<CRUDProps, any> {
|
|||||||
);
|
);
|
||||||
this.lastQuery = store.query;
|
this.lastQuery = store.query;
|
||||||
const data = createObject(store.data, store.query);
|
const data = createObject(store.data, store.query);
|
||||||
isEffectiveApi(api, data)
|
isEffectiveApi(api, data)
|
||||||
? store
|
? store
|
||||||
.fetchInitData(api, data, {
|
.fetchInitData(api, data, {
|
||||||
successMessage: messages && messages.fetchSuccess,
|
successMessage: messages && messages.fetchSuccess,
|
||||||
errorMessage: messages && messages.fetchFailed,
|
errorMessage: messages && messages.fetchFailed,
|
||||||
autoAppend: true,
|
autoAppend: true,
|
||||||
forceReload,
|
forceReload,
|
||||||
loadDataOnce,
|
loadDataOnce,
|
||||||
source,
|
source,
|
||||||
silent,
|
silent,
|
||||||
pageField,
|
pageField,
|
||||||
perPageField,
|
perPageField,
|
||||||
loadDataMode,
|
loadDataMode,
|
||||||
syncResponse2Query
|
syncResponse2Query
|
||||||
})
|
})
|
||||||
.then(value => {
|
.then(value => {
|
||||||
interval &&
|
interval &&
|
||||||
this.mounted &&
|
this.mounted &&
|
||||||
(!stopAutoRefreshWhen ||
|
(!stopAutoRefreshWhen ||
|
||||||
!(
|
!(
|
||||||
(stopAutoRefreshWhenModalIsOpen && store.hasModalOpened) ||
|
(stopAutoRefreshWhenModalIsOpen && store.hasModalOpened) ||
|
||||||
evalExpression(stopAutoRefreshWhen, data)
|
evalExpression(stopAutoRefreshWhen, data)
|
||||||
)) &&
|
)) &&
|
||||||
(this.timer = setTimeout(
|
(this.timer = setTimeout(
|
||||||
silentPolling ? this.silentSearch : this.search,
|
silentPolling ? this.silentSearch : this.search,
|
||||||
Math.max(interval, 3000)
|
Math.max(interval, 3000)
|
||||||
));
|
));
|
||||||
return value;
|
return value;
|
||||||
})
|
})
|
||||||
: source && store.initFromScope(data, source);
|
: source && store.initFromScope(data, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ export interface DateState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DateField extends React.Component<DateProps, DateState> {
|
export class DateField extends React.Component<DateProps, DateState> {
|
||||||
refreshInterval: number;
|
refreshInterval: NodeJS.Timeout;
|
||||||
|
|
||||||
static defaultProps: Partial<DateProps> = {
|
static defaultProps: Partial<DateProps> = {
|
||||||
placeholder: '-',
|
placeholder: '-',
|
||||||
|
@ -9,6 +9,7 @@ import ImageControl from './Image';
|
|||||||
import {Payload} from '../../types';
|
import {Payload} from '../../types';
|
||||||
import {filter} from '../../utils/tpl';
|
import {filter} from '../../utils/tpl';
|
||||||
import Alert from '../../components/Alert2';
|
import Alert from '../../components/Alert2';
|
||||||
|
import {qsstringify} from '../../utils/helper';
|
||||||
|
|
||||||
export interface FileProps extends FormControlProps {
|
export interface FileProps extends FormControlProps {
|
||||||
btnClassName: string;
|
btnClassName: string;
|
||||||
@ -493,9 +494,9 @@ export default class FileControl extends React.Component<FileProps, FileState> {
|
|||||||
...qs.parse(reciever.substring(idx + 1)),
|
...qs.parse(reciever.substring(idx + 1)),
|
||||||
...params
|
...params
|
||||||
};
|
};
|
||||||
reciever = reciever.substring(0, idx) + '?' + qs.stringify(params);
|
reciever = reciever.substring(0, idx) + '?' + qsstringify(params);
|
||||||
} else if (params) {
|
} else if (params) {
|
||||||
reciever += '?' + qs.stringify(params);
|
reciever += '?' + qsstringify(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._send(reciever, fd, {
|
return this._send(reciever, fd, {
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {FormItem, FormControlProps} from './Item';
|
import {FormItem, FormControlProps} from './Item';
|
||||||
import cx from 'classnames';
|
// @require 'cropperjs/dist/cropper.css';
|
||||||
import Cropper from 'react-cropper';
|
import Cropper from 'react-cropper';
|
||||||
import DropZone from 'react-dropzone';
|
import DropZone from 'react-dropzone';
|
||||||
import 'blueimp-canvastoblob';
|
import 'blueimp-canvastoblob';
|
||||||
// @require 'cropperjs/dist/cropper.css';
|
|
||||||
// jest 不能支持这种写法
|
|
||||||
// import 'cropperjs/dist/cropper.css';
|
|
||||||
import find = require('lodash/find');
|
import find = require('lodash/find');
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import {Payload} from '../../types';
|
import {Payload} from '../../types';
|
||||||
import {filter} from '../../utils/tpl';
|
import {filter} from '../../utils/tpl';
|
||||||
import {Switch} from '../../components';
|
import {Switch} from '../../components';
|
||||||
|
import {buildApi} from '../../utils/api';
|
||||||
|
import {createObject, qsstringify} from '../../utils/helper';
|
||||||
|
import {Icon} from '../../components/icons';
|
||||||
|
import Button from '../../components/Button';
|
||||||
|
|
||||||
|
let id = 1;
|
||||||
|
function gennerateId() {
|
||||||
|
return id++;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ImageProps extends FormControlProps {
|
export interface ImageProps extends FormControlProps {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@ -67,8 +73,10 @@ export interface FileValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FileX extends File {
|
export interface FileX extends File {
|
||||||
|
id?: string | number;
|
||||||
preview?: string;
|
preview?: string;
|
||||||
state?: 'init' | 'error' | 'pending' | 'uploading' | 'uploaded' | 'invalid';
|
state?: 'init' | 'error' | 'pending' | 'uploading' | 'uploaded' | 'invalid';
|
||||||
|
progress?: number;
|
||||||
[propName: string]: any;
|
[propName: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +89,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
btnClassName: 'btn-info btn-sm',
|
btnClassName: 'btn-info btn-sm',
|
||||||
hideUploadButton: false,
|
hideUploadButton: false,
|
||||||
compressOptions: {},
|
compressOptions: {},
|
||||||
placeholder: '将图片拖入该区域,或者',
|
placeholder: '点击选择图片或者将图片拖入该区域',
|
||||||
joinValues: true,
|
joinValues: true,
|
||||||
extractValue: false,
|
extractValue: false,
|
||||||
delimiter: ',',
|
delimiter: ',',
|
||||||
@ -106,7 +114,8 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
...(typeof value === 'string'
|
...(typeof value === 'string'
|
||||||
? {
|
? {
|
||||||
value,
|
value,
|
||||||
url: value
|
url: value,
|
||||||
|
id: gennerateId()
|
||||||
}
|
}
|
||||||
: value),
|
: value),
|
||||||
state: 'init'
|
state: 'init'
|
||||||
@ -299,7 +308,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = find(this.state.files, item => item.state === 'pending');
|
const file = find(this.state.files, item => item.state === 'pending') as FileX;
|
||||||
if (file) {
|
if (file) {
|
||||||
this.current = file;
|
this.current = file;
|
||||||
|
|
||||||
@ -309,44 +318,61 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
files: this.state.files.concat()
|
files: this.state.files.concat()
|
||||||
},
|
},
|
||||||
() =>
|
() =>
|
||||||
this.sendFile(file as FileX, (error, file, obj) => {
|
this.sendFile(
|
||||||
const files = this.state.files.concat();
|
file as FileX,
|
||||||
const idx = files.indexOf(file);
|
(error, file, obj) => {
|
||||||
|
const files = this.state.files.concat();
|
||||||
|
const idx = files.indexOf(file);
|
||||||
|
|
||||||
if (!~idx) {
|
if (!~idx) {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
let newFile: FileX | FileValue = file;
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
newFile.state = file.state !== 'uploading' ? file.state : 'error';
|
|
||||||
newFile.error = error;
|
|
||||||
|
|
||||||
if (!this.props.multiple && newFile.state === 'invalid') {
|
|
||||||
files.splice(idx, 1);
|
|
||||||
this.current = null;
|
|
||||||
|
|
||||||
return this.setState(
|
|
||||||
{
|
|
||||||
files: files,
|
|
||||||
error: error
|
|
||||||
},
|
|
||||||
this.tick
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
newFile = obj as FileValue;
|
let newFile: FileX | FileValue = file;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
newFile.state = file.state !== 'uploading' ? file.state : 'error';
|
||||||
|
newFile.error = error;
|
||||||
|
|
||||||
|
if (!this.props.multiple && newFile.state === 'invalid') {
|
||||||
|
files.splice(idx, 1);
|
||||||
|
this.current = null;
|
||||||
|
|
||||||
|
return this.setState(
|
||||||
|
{
|
||||||
|
files: files,
|
||||||
|
error: error
|
||||||
|
},
|
||||||
|
this.tick
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newFile = obj as FileValue;
|
||||||
|
}
|
||||||
|
files.splice(idx, 1, newFile);
|
||||||
|
this.current = null;
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
files: files
|
||||||
|
},
|
||||||
|
this.tick
|
||||||
|
);
|
||||||
|
},
|
||||||
|
progress => {
|
||||||
|
const files = this.state.files.concat();
|
||||||
|
const idx = files.indexOf(file);
|
||||||
|
|
||||||
|
if (!~idx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// file 是个非 File 对象,先不copy了直接改。
|
||||||
|
file.progress = progress;
|
||||||
|
this.setState({
|
||||||
|
files
|
||||||
|
});
|
||||||
}
|
}
|
||||||
files.splice(idx, 1, newFile);
|
)
|
||||||
this.current = null;
|
|
||||||
this.setState(
|
|
||||||
{
|
|
||||||
files: files
|
|
||||||
},
|
|
||||||
this.tick
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.setState(
|
this.setState(
|
||||||
@ -437,7 +463,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlePaste(e: React.ClipboardEvent<any>) {
|
handlePaste(e: React.ClipboardEvent<any>) {
|
||||||
const event = e.nativeEvent;
|
const event = e.nativeEvent as any;
|
||||||
const files: Array<FileX> = [];
|
const files: Array<FileX> = [];
|
||||||
const items = event.clipboardData.items;
|
const items = event.clipboardData.items;
|
||||||
|
|
||||||
@ -449,6 +475,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
}
|
}
|
||||||
|
|
||||||
blob.preview = window.URL.createObjectURL(blob);
|
blob.preview = window.URL.createObjectURL(blob);
|
||||||
|
blob.id = gennerateId();
|
||||||
files.push(blob);
|
files.push(blob);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -504,6 +531,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
}
|
}
|
||||||
|
|
||||||
file.state = 'pending';
|
file.state = 'pending';
|
||||||
|
file.id = gennerateId();
|
||||||
if (!file.preview || !file.url) {
|
if (!file.preview || !file.url) {
|
||||||
file.preview = window.URL.createObjectURL(file);
|
file.preview = window.URL.createObjectURL(file);
|
||||||
}
|
}
|
||||||
@ -530,11 +558,15 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendFile(file: FileX, cb: (error: null | string, file: FileX, obj?: FileValue) => void) {
|
sendFile(
|
||||||
|
file: FileX,
|
||||||
|
cb: (error: null | string, file: FileX, obj?: FileValue) => void,
|
||||||
|
onProgress: (progress: number) => void
|
||||||
|
) {
|
||||||
const {limit} = this.props;
|
const {limit} = this.props;
|
||||||
|
|
||||||
if (!limit) {
|
if (!limit) {
|
||||||
return this._upload(file, cb);
|
return this._upload(file, cb, onProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
@ -564,13 +596,17 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
file.state = 'invalid';
|
file.state = 'invalid';
|
||||||
cb(error, file);
|
cb(error, file);
|
||||||
} else {
|
} else {
|
||||||
this._upload(file, cb);
|
this._upload(file, cb, onProgress);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
image.src = (file.preview || file.url) as string;
|
image.src = (file.preview || file.url) as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
_upload(file: Blob, cb: (error: null | string, file: Blob, obj?: FileValue) => void) {
|
_upload(
|
||||||
|
file: Blob,
|
||||||
|
cb: (error: null | string, file: Blob, obj?: FileValue) => void,
|
||||||
|
onProgress: (progress: number) => void
|
||||||
|
) {
|
||||||
let compressOptions = this.state.compressOptions;
|
let compressOptions = this.state.compressOptions;
|
||||||
|
|
||||||
if (this.props.showCompressOptions) {
|
if (this.props.showCompressOptions) {
|
||||||
@ -581,7 +617,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this._send(file, this.props.reciever as string, {compress: this.state.compress, compressOptions})
|
this._send(file, this.props.reciever as string, {compress: this.state.compress, compressOptions}, onProgress)
|
||||||
.then((ret: Payload) => {
|
.then((ret: Payload) => {
|
||||||
if (ret.status) {
|
if (ret.status) {
|
||||||
throw new Error(ret.msg || '上传失败, 请重试');
|
throw new Error(ret.msg || '上传失败, 请重试');
|
||||||
@ -598,29 +634,35 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
.catch(error => cb(error.message || '上传失败,请重试', file));
|
.catch(error => cb(error.message || '上传失败,请重试', file));
|
||||||
}
|
}
|
||||||
|
|
||||||
_send(file: Blob, reciever: string, params: object): Promise<Payload> {
|
_send(file: Blob, reciever: string, params: object, onProgress: (progress: number) => void): Promise<Payload> {
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
const data = this.props.data;
|
const data = this.props.data;
|
||||||
reciever = filter(reciever, data);
|
const api = buildApi(reciever, createObject(data, params), {
|
||||||
|
method: 'post'
|
||||||
|
});
|
||||||
const fileField = this.props.fileField || 'file';
|
const fileField = this.props.fileField || 'file';
|
||||||
fd.append(fileField, file, (file as File).name);
|
fd.append(fileField, file, (file as File).name);
|
||||||
|
|
||||||
const idx = reciever.indexOf('?');
|
const idx = api.url.indexOf('?');
|
||||||
|
|
||||||
if (~idx && params) {
|
if (~idx && params) {
|
||||||
params = {
|
params = {
|
||||||
...qs.parse(reciever.substring(idx + 1)),
|
...qs.parse(reciever.substring(idx + 1)),
|
||||||
...params
|
...params
|
||||||
};
|
};
|
||||||
reciever = reciever.substring(0, idx) + '?' + qs.stringify(params);
|
api.url = api.url.substring(0, idx) + '?' + qsstringify(params);
|
||||||
} else if (params) {
|
} else if (params) {
|
||||||
reciever += '?' + qs.stringify(params);
|
api.url += '?' + qsstringify(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// params && Object.keys(params).forEach(key => {
|
if (api.data) {
|
||||||
// const value = (params as any)[key];
|
qsstringify(api.data)
|
||||||
// fd.append(key, value);
|
.split('&')
|
||||||
// });
|
.forEach(item => {
|
||||||
|
let parts = item.split('=');
|
||||||
|
fd.append(parts[0], parts[1]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const env = this.props.env;
|
const env = this.props.env;
|
||||||
|
|
||||||
@ -628,8 +670,9 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
throw new Error('fetcher is required');
|
throw new Error('fetcher is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
return env.fetcher(reciever, fd, {
|
return env.fetcher(api, fd, {
|
||||||
method: 'post'
|
method: 'post',
|
||||||
|
onUploadProgress: (event: {loaded: number; total: number}) => onProgress(event.loaded / event.total)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -751,7 +794,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
classPrefix: ns,
|
classnames: cx,
|
||||||
placeholder,
|
placeholder,
|
||||||
disabled,
|
disabled,
|
||||||
multiple,
|
multiple,
|
||||||
@ -768,7 +811,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
const hasPending = files.some(file => file.state == 'pending');
|
const hasPending = files.some(file => file.state == 'pending');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(`${ns}ImageControl`, className)} tabIndex={-1} onPaste={this.handlePaste}>
|
<div className={cx(`ImageControl`, className)} tabIndex={-1} onPaste={this.handlePaste}>
|
||||||
{cropFile ? (
|
{cropFile ? (
|
||||||
<div className="cropper-wrapper">
|
<div className="cropper-wrapper">
|
||||||
<Cropper {...crop} ref="cropper" src={cropFile.preview} />
|
<Cropper {...crop} ref="cropper" src={cropFile.preview} />
|
||||||
@ -782,11 +825,11 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
) : (
|
) : (
|
||||||
<DropZone
|
<DropZone
|
||||||
key="drop-zone"
|
key="drop-zone"
|
||||||
className={cx('drop-zone', {
|
className={cx('ImageControl-dropzone', {
|
||||||
disabled,
|
disabled,
|
||||||
'has-files': !!files.length
|
'is-empty': !files.length
|
||||||
})}
|
})}
|
||||||
activeClassName="drop-zone-active"
|
activeClassName="is-active"
|
||||||
ref="dropzone"
|
ref="dropzone"
|
||||||
onDrop={this.handleDrop}
|
onDrop={this.handleDrop}
|
||||||
onDropRejected={this.handleDropRejected}
|
onDropRejected={this.handleDropRejected}
|
||||||
@ -794,109 +837,131 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
|
|||||||
accept={accept}
|
accept={accept}
|
||||||
multiple={multiple}
|
multiple={multiple}
|
||||||
>
|
>
|
||||||
{files && files.length ? (
|
{files && files.length
|
||||||
<div
|
? files.map((file, key) => (
|
||||||
className={cx('image-list clearfix', {
|
<div
|
||||||
'image-list-multiple': multiple
|
key={file.id || key}
|
||||||
})}
|
className={cx('ImageControl-item', {
|
||||||
|
'is-uploaded': file.state !== 'uploading',
|
||||||
|
'is-invalid': file.state === 'error' || file.state === 'invalid'
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{file.state === 'invalid' || file.state === 'error' ? (
|
||||||
|
<a
|
||||||
|
className={cx('ImageControl-retryBtn', {'is-disabled': disabled})}
|
||||||
|
onClick={this.handleSelect}
|
||||||
|
>
|
||||||
|
<Icon icon="retry" className="icon" />
|
||||||
|
<p className="ImageControl-itemInfoError">重新上传</p>
|
||||||
|
</a>
|
||||||
|
) : file.state === 'uploading' ? (
|
||||||
|
<>
|
||||||
|
<a
|
||||||
|
onClick={this.removeFile.bind(this, file, key)}
|
||||||
|
key="clear"
|
||||||
|
className={cx('ImageControl-itemClear')}
|
||||||
|
data-tooltip="移除"
|
||||||
|
>
|
||||||
|
<Icon icon="close" className="icon" />
|
||||||
|
</a>
|
||||||
|
<div key="info" className={cx('ImageControl-itemInfo')}>
|
||||||
|
<p>文件上传中</p>
|
||||||
|
<div className={cx('ImageControl-progress')}>
|
||||||
|
<span
|
||||||
|
style={{width: `${Math.round(file.progress * 100)}%`}}
|
||||||
|
className={cx('ImageControl-progressValue')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div key="image" className={cx('ImageControl-itemImageWrap')}>
|
||||||
|
<img
|
||||||
|
onLoad={this.handleImageLoaded.bind(this, key)}
|
||||||
|
src={file.url || file.preview}
|
||||||
|
alt={file.name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div key="overlay" className={cx('ImageControl-itemOverlay')}>
|
||||||
|
{file.info ? (
|
||||||
|
[
|
||||||
|
<div key="1">
|
||||||
|
{file.info.width} x {file.info.height}
|
||||||
|
</div>,
|
||||||
|
file.info.len ? (
|
||||||
|
<div key="2">
|
||||||
|
{ImageControl.formatFileSize(file.info.len)}
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
]
|
||||||
|
) : (
|
||||||
|
<div>...</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!disabled ? (
|
||||||
|
<a
|
||||||
|
data-tooltip="查看大图"
|
||||||
|
data-position="bottom"
|
||||||
|
target="_blank"
|
||||||
|
href={file.url || file.preview}
|
||||||
|
>
|
||||||
|
<Icon icon="view" className="icon" />
|
||||||
|
</a>
|
||||||
|
) : null}
|
||||||
|
{!!crop && !disabled ? (
|
||||||
|
<a
|
||||||
|
data-tooltip="裁剪图片"
|
||||||
|
data-position="bottom"
|
||||||
|
onClick={this.editImage.bind(this, key)}
|
||||||
|
>
|
||||||
|
<Icon icon="pencil" className="icon" />
|
||||||
|
</a>
|
||||||
|
) : null}
|
||||||
|
{!disabled ? (
|
||||||
|
<a
|
||||||
|
data-tooltip="移除"
|
||||||
|
data-position="bottom"
|
||||||
|
onClick={this.removeFile.bind(this, file, key)}
|
||||||
|
>
|
||||||
|
<Icon icon="remove" className="icon" />
|
||||||
|
</a>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{(multiple && (!maxLength || files.length < maxLength)) || (!multiple && !files.length) ? (
|
||||||
|
<label
|
||||||
|
className={cx('ImageControl-addBtn', {'is-disabled': disabled})}
|
||||||
|
onClick={this.handleSelect}
|
||||||
|
data-tooltip={placeholder}
|
||||||
|
data-position="right"
|
||||||
>
|
>
|
||||||
{files.map((file, key) => (
|
<Icon icon="plus" className="icon" />
|
||||||
<div
|
</label>
|
||||||
key={key}
|
) : null}
|
||||||
className={cx('image-item pull-left', {
|
|
||||||
uploaded: file.state !== 'uploading',
|
|
||||||
invalid: file.state === 'error' || file.state == 'invalid'
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div className="img-wrapper">
|
|
||||||
<img
|
|
||||||
onLoad={this.handleImageLoaded.bind(this, key)}
|
|
||||||
src={file.url || file.preview}
|
|
||||||
alt={file.name}
|
|
||||||
className="img-rounded"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{file.info ? (
|
|
||||||
[
|
|
||||||
<p key="1">
|
|
||||||
{file.info.width} x {file.info.height}
|
|
||||||
</p>,
|
|
||||||
file.info.len ? (
|
|
||||||
<p key="2">{ImageControl.formatFileSize(file.info.len)}</p>
|
|
||||||
) : null
|
|
||||||
]
|
|
||||||
) : (
|
|
||||||
<p>...</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{file.error ? <p className="text-danger">{file.error}</p> : null}
|
|
||||||
|
|
||||||
<div className="image-overlay">
|
|
||||||
{file.state === 'uploading' ? (
|
|
||||||
<i className="fa fa-spinner fa-spin fa-2x fa-fw" />
|
|
||||||
) : null}
|
|
||||||
{!disabled && file.state !== 'uploading' ? (
|
|
||||||
<button
|
|
||||||
onClick={this.removeFile.bind(this, file, key)}
|
|
||||||
type="button"
|
|
||||||
className={cx('close', {'crop-close': !!crop})}
|
|
||||||
>
|
|
||||||
<span>×</span>
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
{!!crop && !disabled && file.state !== 'uploading' ? (
|
|
||||||
<button
|
|
||||||
onClick={this.editImage.bind(this, key)}
|
|
||||||
type="button"
|
|
||||||
className="edit"
|
|
||||||
>
|
|
||||||
<i className="fa fa-pencil" />
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
{!disabled && file.state !== 'uploading' ? (
|
|
||||||
<a target="_blank" href={file.url || file.preview} className="view">
|
|
||||||
<i className="fa fa-search" />
|
|
||||||
</a>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{(multiple && (!maxLength || files.length < maxLength)) ||
|
|
||||||
(!multiple && !files.length) ? (
|
|
||||||
<label className={cx('image-add-btn', {disabled})} onClick={this.handleSelect}>
|
|
||||||
<i className="fa fa-plus fa-3x" />
|
|
||||||
</label>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={error ? 'text-danger' : undefined}>
|
|
||||||
{error || placeholder}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={cx('btn m-l-sm', btnClassName)}
|
|
||||||
disabled={disabled}
|
|
||||||
onClick={this.handleSelect}
|
|
||||||
>
|
|
||||||
<i className="fa fa-cloud-upload" /> 选择文件
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</DropZone>
|
</DropZone>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.renderCompressOptions()}
|
{this.renderCompressOptions()}
|
||||||
|
|
||||||
{!autoUpload && !hideUploadButton && files.length ? (
|
{!autoUpload && !hideUploadButton && files.length ? (
|
||||||
<button
|
<Button
|
||||||
type="button"
|
level="default"
|
||||||
className={cx('btn m-r-xs', btnUploadClassName)}
|
className={cx('ImageControl-uploadBtn', btnUploadClassName)}
|
||||||
disabled={!hasPending}
|
disabled={!hasPending}
|
||||||
onClick={this.toggleUpload}
|
onClick={this.toggleUpload}
|
||||||
>
|
>
|
||||||
{uploading ? '暂停上传' : '开始上传'}
|
{uploading ? '暂停上传' : '开始上传'}
|
||||||
</button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{error ? <div className={cx('ImageControl-errorMsg')}>{error}</div> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -200,27 +200,20 @@ export function registerOptionsControl(config: OptionsConfig) {
|
|||||||
normalizeValue() {
|
normalizeValue() {
|
||||||
const {joinValues, extractValue, value, multiple, formItem, valueField} = this.props;
|
const {joinValues, extractValue, value, multiple, formItem, valueField} = this.props;
|
||||||
|
|
||||||
if (
|
if (!formItem || joinValues !== false || !formItem.options.length) {
|
||||||
formItem &&
|
return;
|
||||||
joinValues === false &&
|
|
||||||
extractValue === false &&
|
|
||||||
(typeof value === 'string' || typeof value === 'number') &&
|
|
||||||
formItem.options.length
|
|
||||||
) {
|
|
||||||
formItem.changeValue(multiple ? formItem.selectedOptions.concat() : formItem.selectedOptions[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (extractValue === false && (typeof value === 'string' || typeof value === 'number')) {
|
||||||
formItem &&
|
formItem.changeValue(multiple ? formItem.selectedOptions.concat() : formItem.selectedOptions[0]);
|
||||||
joinValues === false &&
|
} else if (
|
||||||
extractValue === true &&
|
extractValue === true &&
|
||||||
value &&
|
value &&
|
||||||
!(
|
!(
|
||||||
(Array.isArray(value) && value.every(val => typeof val === 'string' || typeof val === 'number')) ||
|
(Array.isArray(value) && value.every(val => typeof val === 'string' || typeof val === 'number')) ||
|
||||||
typeof value === 'string' ||
|
typeof value === 'string' ||
|
||||||
typeof value === 'number'
|
typeof value === 'number'
|
||||||
) &&
|
)
|
||||||
formItem.options.length
|
|
||||||
) {
|
) {
|
||||||
const selectedOptions = formItem.selectedOptions.map(
|
const selectedOptions = formItem.selectedOptions.map(
|
||||||
(selectedOption: Option) => selectedOption[valueField || 'value']
|
(selectedOption: Option) => selectedOption[valueField || 'value']
|
||||||
|
@ -4,7 +4,7 @@ import cx from 'classnames';
|
|||||||
import Button from '../../components/Button';
|
import Button from '../../components/Button';
|
||||||
import {SchemaNode, Schema, Action} from '../../types';
|
import {SchemaNode, Schema, Action} from '../../types';
|
||||||
import find = require('lodash/find');
|
import find = require('lodash/find');
|
||||||
import {anyChanged, autobind, getVariable, noop} from '../../utils/helper';
|
import {anyChanged, autobind, getVariable, noop, createObject} from '../../utils/helper';
|
||||||
import findIndex = require('lodash/findIndex');
|
import findIndex = require('lodash/findIndex');
|
||||||
import Html from '../../components/Html';
|
import Html from '../../components/Html';
|
||||||
import {filter} from '../../utils/tpl';
|
import {filter} from '../../utils/tpl';
|
||||||
@ -59,6 +59,10 @@ export default class PickerControl extends React.PureComponent<PickerProps, any>
|
|||||||
|
|
||||||
input: React.RefObject<HTMLInputElement> = React.createRef();
|
input: React.RefObject<HTMLInputElement> = React.createRef();
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.fetchOptions();
|
||||||
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps: PickerProps) {
|
componentWillReceiveProps(nextProps: PickerProps) {
|
||||||
const props = this.props;
|
const props = this.props;
|
||||||
|
|
||||||
@ -69,6 +73,38 @@ export default class PickerControl extends React.PureComponent<PickerProps, any>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: PickerProps) {
|
||||||
|
const props = this.props;
|
||||||
|
|
||||||
|
if (props.value !== prevProps.value) {
|
||||||
|
this.fetchOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchOptions() {
|
||||||
|
const {value, formItem, valueField, labelField, source, data} = this.props;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!source ||
|
||||||
|
!formItem ||
|
||||||
|
!formItem.selectedOptions.length ||
|
||||||
|
formItem.selectedOptions[0][valueField || 'value'] !== formItem.selectedOptions[0][labelField || 'label']
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
formItem.loadOptions(
|
||||||
|
source,
|
||||||
|
createObject(data, {
|
||||||
|
value: value,
|
||||||
|
op: 'loadOptions'
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
autoAppend: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
buildSchema(props: PickerProps) {
|
buildSchema(props: PickerProps) {
|
||||||
return {
|
return {
|
||||||
...props.pickerSchema,
|
...props.pickerSchema,
|
||||||
|
@ -16,6 +16,7 @@ function loadComponent(): Promise<React.ReactType> {
|
|||||||
|
|
||||||
export default class RichTextControl extends React.Component<RichTextProps, any> {
|
export default class RichTextControl extends React.Component<RichTextProps, any> {
|
||||||
static defaultProps: Partial<RichTextProps> = {
|
static defaultProps: Partial<RichTextProps> = {
|
||||||
|
imageEditable: true,
|
||||||
reciever: '/api/upload/image',
|
reciever: '/api/upload/image',
|
||||||
videoReciever: '/api/upload/video',
|
videoReciever: '/api/upload/video',
|
||||||
placeholder: '请输入',
|
placeholder: '请输入',
|
||||||
|
@ -168,7 +168,7 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
let {
|
||||||
autoComplete,
|
autoComplete,
|
||||||
searchable,
|
searchable,
|
||||||
options,
|
options,
|
||||||
@ -184,9 +184,15 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
|||||||
classnames,
|
classnames,
|
||||||
creatable,
|
creatable,
|
||||||
inline,
|
inline,
|
||||||
|
noResultsText,
|
||||||
|
render,
|
||||||
...rest
|
...rest
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
if (noResultsText && /<\w+/.test(noResultsText)) {
|
||||||
|
noResultsText = render('noResultText', noResultsText);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(`${classPrefix}SelectControl`, className)}>
|
<div className={cx(`${classPrefix}SelectControl`, className)}>
|
||||||
<Select
|
<Select
|
||||||
@ -202,8 +208,7 @@ export default class SelectControl extends React.Component<SelectProps, any> {
|
|||||||
searchable={autoComplete || creatable ? true : searchable}
|
searchable={autoComplete || creatable ? true : searchable}
|
||||||
onChange={this.changeValue}
|
onChange={this.changeValue}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
cache={false}
|
noResultsText={noResultsText}
|
||||||
joinValues={false}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -179,15 +179,17 @@ export default class SubFormControl extends React.PureComponent<SubFormProps, Su
|
|||||||
data-tooltip="编辑详情"
|
data-tooltip="编辑详情"
|
||||||
data-position="bottom"
|
data-position="bottom"
|
||||||
>
|
>
|
||||||
{(value && labelField && value[labelField] && stripTag(value[labelField]))
|
{(value && labelField && value[labelField] && stripTag(value[labelField])) ||
|
||||||
|| render('label',
|
render(
|
||||||
{
|
'label',
|
||||||
type: 'tpl',
|
{
|
||||||
tpl: btnLabel
|
type: 'tpl',
|
||||||
},
|
tpl: btnLabel
|
||||||
{
|
},
|
||||||
data
|
{
|
||||||
})}
|
data
|
||||||
|
}
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
@ -225,16 +227,17 @@ export default class SubFormControl extends React.PureComponent<SubFormProps, Su
|
|||||||
data-position="bottom"
|
data-position="bottom"
|
||||||
>
|
>
|
||||||
<span className={`${ns}SubForm-valueLabel`}>
|
<span className={`${ns}SubForm-valueLabel`}>
|
||||||
{(value && labelField && value[labelField] && stripTag(value[labelField]))
|
{(value && labelField && value[labelField] && stripTag(value[labelField])) ||
|
||||||
|| render('label',
|
render(
|
||||||
{
|
'label',
|
||||||
type: 'tpl',
|
{
|
||||||
tpl: btnLabel
|
type: 'tpl',
|
||||||
},
|
tpl: btnLabel
|
||||||
{
|
},
|
||||||
data
|
{
|
||||||
}
|
data
|
||||||
)}}
|
}
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -248,16 +251,12 @@ export default class SubFormControl extends React.PureComponent<SubFormProps, Su
|
|||||||
return (
|
return (
|
||||||
<div className={cx(`${ns}SubFormControl`, className)}>
|
<div className={cx(`${ns}SubFormControl`, className)}>
|
||||||
{multiple ? this.renderMultipe() : this.renderSingle()}
|
{multiple ? this.renderMultipe() : this.renderSingle()}
|
||||||
{openedIndex !== -1
|
{render(`dalog/${openedIndex}`, this.buildDialogSchema(), {
|
||||||
? render(`dalog/${openedIndex}`, this.buildDialogSchema(), {
|
show: openedIndex !== -1,
|
||||||
onClose: this.close,
|
onClose: this.close,
|
||||||
onConfirm: this.handleDialogConfirm,
|
onConfirm: this.handleDialogConfirm,
|
||||||
data: createObject(
|
data: createObject(data, (multiple ? Array.isArray(value) && value[openedIndex] : value) || {})
|
||||||
data,
|
})}
|
||||||
(multiple ? Array.isArray(value) && value[openedIndex] : value) || {}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
: null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {types, getParent, flow, getEnv, getRoot} from 'mobx-state-tree';
|
import {types, getParent, flow, getEnv, getRoot} from 'mobx-state-tree';
|
||||||
import {IRendererStore} from './index';
|
import {IRendererStore} from './index';
|
||||||
import {ServiceStore} from './service';
|
import {ServiceStore} from './service';
|
||||||
import {extendObject, createObject, isObjectShallowModified, sortArray, isEmpty} from '../utils/helper';
|
import {extendObject, createObject, isObjectShallowModified, sortArray, isEmpty, qsstringify} from '../utils/helper';
|
||||||
import {Api, Payload, fetchOptions, Action} from '../types';
|
import {Api, Payload, fetchOptions, Action} from '../types';
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import pick = require('lodash/pick');
|
import pick = require('lodash/pick');
|
||||||
@ -91,7 +91,7 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
|
|||||||
|
|
||||||
updater &&
|
updater &&
|
||||||
isObjectShallowModified(originQuery, self.query, false) &&
|
isObjectShallowModified(originQuery, self.query, false) &&
|
||||||
setTimeout(() => updater(`?${qs.stringify(self.query, {encodeValuesOnly: true})}`), 4);
|
setTimeout(() => updater(`?${qsstringify(self.query)}`), 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchInitData: (
|
const fetchInitData: (
|
||||||
|
@ -3,7 +3,7 @@ import {fetcherConfig} from '../factory';
|
|||||||
import {tokenize, dataMapping} from './tpl-builtin';
|
import {tokenize, dataMapping} from './tpl-builtin';
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import {evalExpression} from './tpl';
|
import {evalExpression} from './tpl';
|
||||||
import {isObject, isObjectShallowModified, hasFile, object2formData} from './helper';
|
import {isObject, isObjectShallowModified, hasFile, object2formData, qsstringify} from './helper';
|
||||||
|
|
||||||
const rSchema = /(?:^|raw\:)(get|post|put|delete|patch|options|head):/i;
|
const rSchema = /(?:^|raw\:)(get|post|put|delete|patch|options|head):/i;
|
||||||
|
|
||||||
@ -81,9 +81,9 @@ export function buildApi(
|
|||||||
...qs.parse(api.url.substring(idx + 1)),
|
...qs.parse(api.url.substring(idx + 1)),
|
||||||
...api.data
|
...api.data
|
||||||
};
|
};
|
||||||
api.url = api.url.substring(0, idx) + '?' + qs.stringify(params);
|
api.url = api.url.substring(0, idx) + '?' + qsstringify(params);
|
||||||
} else {
|
} else {
|
||||||
api.url += '?' + qs.stringify(api.data);
|
api.url += '?' + qsstringify(api.data);
|
||||||
}
|
}
|
||||||
delete api.data;
|
delete api.data;
|
||||||
}
|
}
|
||||||
|
@ -777,14 +777,25 @@ export function hasFile(object: any): boolean {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function qsstringify(
|
||||||
|
data: any,
|
||||||
|
options: any = {
|
||||||
|
arrayFormat: 'brackets',
|
||||||
|
encodeValuesOnly: true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
return qs.stringify(data, options);
|
||||||
|
}
|
||||||
|
|
||||||
export function object2formData(
|
export function object2formData(
|
||||||
data: any,
|
data: any,
|
||||||
options: any = {
|
options: any = {
|
||||||
arrayForamt: 'brackets'
|
arrayFormat: 'brackets',
|
||||||
}
|
encodeValuesOnly: true
|
||||||
|
},
|
||||||
|
fd: FormData = new FormData()
|
||||||
): any {
|
): any {
|
||||||
let others: any = {};
|
let others: any = {};
|
||||||
const fd = new FormData();
|
|
||||||
Object.keys(data).forEach(key => {
|
Object.keys(data).forEach(key => {
|
||||||
const value = data[key];
|
const value = data[key];
|
||||||
|
|
||||||
@ -798,7 +809,7 @@ export function object2formData(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 因为 key 的格式太多了,偷个懒,用 qs 来处理吧。
|
// 因为 key 的格式太多了,偷个懒,用 qs 来处理吧。
|
||||||
qs.stringify(others, options)
|
qsstringify(others, options)
|
||||||
.split('&')
|
.split('&')
|
||||||
.forEach(item => {
|
.forEach(item => {
|
||||||
let parts = item.split('=');
|
let parts = item.split('=');
|
||||||
|
Loading…
Reference in New Issue
Block a user