Merge pull request #296 from 2betop/master

Image 更新 ui
This commit is contained in:
liaoxuezhi 2019-10-22 19:04:28 +08:00 committed by GitHub
commit 112c2b7e15
22 changed files with 613 additions and 437 deletions

View File

@ -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",

View File

@ -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,
'SF Pro SC',
'SF Pro Text',
'Helvetica Neue',
Helvetica,
'PingFang SC',
'Segoe UI',
Roboto,
'Hiragino Sans GB',
'Arial',
'microsoft yahei ui',
'Microsoft YaHei',
SimSun,
sans-serif !default; sans-serif !default;
$fontFamilyMonospace: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !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,7 +768,9 @@ $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,
background-color 0.15s ease-in-out,
border-color 0.15s ease-in-out,
box-shadow 0.15s ease-in-out !default; box-shadow 0.15s ease-in-out !default;
$Button--primary-bg: $primary !default; $Button--primary-bg: $primary !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;

View File

@ -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;
} }
&.has-files { &.is-disabled {
cursor: default;
border: 2px dashed transparent;
}
}
.drop-zone-wrapper:focus .drop-zone:not(.disabled) {
color: $text-color;
border: $Form-input-borderWidth * 2 dashed $Form-input-onFocused-borderColor;
height: 90px;
padding-bottom: 20px;
margin-bottom: 10px;
&: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; pointer-events: none;
border: px2rem(1px) solid $ImageControl-addBtn-onDisabled-border;
background: $ImageControl-addBtn-onDisabled-bg;
color: $ImageControl-addBtn-onDisabled-color;
}
} }
.drop-zone-active, &-dropzone.is-active &-addBtn {
.drop-zone-active.has-files, border-color: $ImageControl-addBtn-onHover-border;
.drop-zone:hover:not(.disabled):not(.has-files) { background: $ImageControl-addBtn-onHover-bg;
color: $text-color; color: $ImageControl-addBtn-onHover-color;
border: $Form-input-borderWidth * 2 dashed $Form-input-onFocused-borderColor;
} }
.image-list { &-item {
outline: none; border: $borderWidth solid $borderColor;
margin: -5px; vertical-align: top;
padding: px2rem(5px);
.image-item { display: inline-block;
margin-right: px2rem(15px);
margin-right: px2rem(15px);
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;
top: 0; width: px2rem(108px);
left: 0; height: px2rem(108px);
right: 0; display: none;
bottom: 0; top: px2rem(5px);
display: flex; left: px2rem(5px);
flex-direction: column;
justify-content: center;
align-content: center;
padding: 10px;
> a,
> button {
flex: 1;
outline: none;
display: flex;
color: #fff;
background: transparent;
border: 0;
text-decoration: none;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
opacity: 0.6; align-content: center;
flex-wrap: wrap;
color: #fff;
&:hover { >div {
opacity: 1; width: 100%;
}
}
}
p {
margin: 0;
text-align: center; text-align: center;
} margin-bottom: 5px;
} }
&.image-list-multiple .image-item .img-wrapper { >a {
height: 90px;
display: table-cell;
vertical-align: middle;
}
.image-add-btn {
width: 90px;
height: 110px;
line-height: 110px;
margin: 5px;
cursor: pointer; cursor: pointer;
float: left; color: #fff;
display: inline-block;
&:hover .fa { padding: 0 5px;
color: $dark; line-height: 1;
font-size: px2rem(16px);
} }
} }
.image-item.uploaded { &-item:hover &-itemOverlay {
.fa-spinner {
display: none;
}
.image-overlay {
display: none;
}
&:hover {
.image-overlay {
display: flex; display: flex;
} }
}
}
}
.cropper-wrapper { &-itemClear {
position: relative;
img {
max-width: 100%;
max-height: 400px;
}
.btn {
position: absolute; position: absolute;
bottom: 0; cursor: pointer;
right: 0;
}
.btn:nth-child(2n + 1) { color: #999;
bottom: 40px; top: 5px;
right: 4px; right: 5px;
line-height: 1;
>svg {
top: 0;
width: 10px;
height: 10px;
} }
} }
@media (min-width: 768px) { &-itemInfo {
.amis-image-control.form-contorl-inline, display: inline-flex;
.form-group-inline .amis-image-control { width: 110px;
display: inline-block; height: 110px;
min-width: 280px; justify-content: center;
align-items: center;
align-content: center;
flex-wrap: wrap;
>p {
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 {
color: #333;
text-decoration: none;
}
>p {
width: 100%;
text-align: center;
color: $danger;
margin: 10px 0 0;
}
}
&-errorMsg {
color: $danger;
margin: 5px 0 0;
}
&-uploadBtn {
margin-top: 5px;
} }
} }

View File

@ -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;

View File

@ -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);
} }
}); });

View File

@ -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`),

View File

@ -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
View 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
View 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
View 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

View File

@ -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(
{ {

View File

@ -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: '-',

View File

@ -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, {

View File

@ -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,7 +318,9 @@ 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(
file as FileX,
(error, file, obj) => {
const files = this.state.files.concat(); const files = this.state.files.concat();
const idx = files.indexOf(file); const idx = files.indexOf(file);
@ -346,7 +357,22 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
}, },
this.tick 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
});
}
)
); );
} 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
? files.map((file, key) => (
<div <div
className={cx('image-list clearfix', { key={file.id || key}
'image-list-multiple': multiple className={cx('ImageControl-item', {
'is-uploaded': file.state !== 'uploading',
'is-invalid': file.state === 'error' || file.state === 'invalid'
})} })}
> >
{files.map((file, key) => ( {file.state === 'invalid' || file.state === 'error' ? (
<div <a
key={key} className={cx('ImageControl-retryBtn', {'is-disabled': disabled})}
className={cx('image-item pull-left', { onClick={this.handleSelect}
uploaded: file.state !== 'uploading',
invalid: file.state === 'error' || file.state == 'invalid'
})}
> >
<div className="img-wrapper"> <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 <img
onLoad={this.handleImageLoaded.bind(this, key)} onLoad={this.handleImageLoaded.bind(this, key)}
src={file.url || file.preview} src={file.url || file.preview}
alt={file.name} alt={file.name}
className="img-rounded"
/> />
</div> </div>
<div key="overlay" className={cx('ImageControl-itemOverlay')}>
{file.info ? ( {file.info ? (
[ [
<p key="1"> <div key="1">
{file.info.width} x {file.info.height} {file.info.width} x {file.info.height}
</p>, </div>,
file.info.len ? ( file.info.len ? (
<p key="2">{ImageControl.formatFileSize(file.info.len)}</p> <div key="2">
{ImageControl.formatFileSize(file.info.len)}
</div>
) : null ) : null
] ]
) : ( ) : (
<p>...</p> <div>...</div>
)} )}
{file.error ? <p className="text-danger">{file.error}</p> : null} {!disabled ? (
<a
<div className="image-overlay"> data-tooltip="查看大图"
{file.state === 'uploading' ? ( data-position="bottom"
<i className="fa fa-spinner fa-spin fa-2x fa-fw" /> target="_blank"
) : null} href={file.url || file.preview}
{!disabled && file.state !== 'uploading' ? (
<button
onClick={this.removeFile.bind(this, file, key)}
type="button"
className={cx('close', {'crop-close': !!crop})}
> >
<span>&times;</span> <Icon icon="view" className="icon" />
</button> </a>
) : null} ) : null}
{!!crop && !disabled && file.state !== 'uploading' ? ( {!!crop && !disabled ? (
<button <a
data-tooltip="裁剪图片"
data-position="bottom"
onClick={this.editImage.bind(this, key)} onClick={this.editImage.bind(this, key)}
type="button"
className="edit"
> >
<i className="fa fa-pencil" /> <Icon icon="pencil" className="icon" />
</button> </a>
) : null} ) : null}
{!disabled && file.state !== 'uploading' ? ( {!disabled ? (
<a target="_blank" href={file.url || file.preview} className="view"> <a
<i className="fa fa-search" /> data-tooltip="移除"
data-position="bottom"
onClick={this.removeFile.bind(this, file, key)}
>
<Icon icon="remove" className="icon" />
</a> </a>
) : null} ) : null}
</div> </div>
</>
)}
</div> </div>
))} ))
: null}
{(multiple && (!maxLength || files.length < maxLength)) || {(multiple && (!maxLength || files.length < maxLength)) || (!multiple && !files.length) ? (
(!multiple && !files.length) ? ( <label
<label className={cx('image-add-btn', {disabled})} onClick={this.handleSelect}> className={cx('ImageControl-addBtn', {'is-disabled': disabled})}
<i className="fa fa-plus fa-3x" /> onClick={this.handleSelect}
data-tooltip={placeholder}
data-position="right"
>
<Icon icon="plus" className="icon" />
</label> </label>
) : null} ) : 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>
); );
} }

View File

@ -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']

View File

@ -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,

View File

@ -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: '请输入',

View File

@ -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>
); );

View File

@ -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', type: 'tpl',
tpl: btnLabel tpl: btnLabel
}, },
{ {
data data
})} }
)}
</span> </span>
</div> </div>
)) ))
@ -225,8 +227,9 @@ 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', type: 'tpl',
tpl: btnLabel tpl: btnLabel
@ -234,7 +237,7 @@ export default class SubFormControl extends React.PureComponent<SubFormProps, Su
{ {
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>
); );
} }

View File

@ -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: (

View File

@ -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;
} }

View File

@ -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('=');