Image 更改 ui

This commit is contained in:
liaoxuezhi 2019-10-21 19:03:56 +08:00
parent f8954ae968
commit 28e090cc20
7 changed files with 478 additions and 187 deletions

View File

@ -35,10 +35,28 @@ $dark: $gray800 !default;
$remFactor: 16px !default;
// 字体相关
$fontFamilySansSerif: -apple-system, 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;
$fontFamilyMonospace: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace !default;
$fontFamilySansSerif: -apple-system,
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;
$fontFamilyMonospace: SFMono-Regular,
Menlo,
Monaco,
Consolas,
'Liberation Mono',
'Courier New',
monospace !default;
$fontFamilyBase: $fontFamilySansSerif !default;
$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;
// 窗口适配
$breakpoints: (
xs: 0,
$breakpoints: (xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px
) !default;
xl: 1200px) !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-headerBar-borderBottom: none !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-lgHeight: 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-linkMargin: 0 40px 0 0 !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-bg: darken($body-bg, 5%) !default;
@ -737,7 +755,8 @@ $Button--lg-lineHeight: 24 / 20 !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-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-onActive-boxShadow: inset 0 3px 5px rgba($black, 0.125) !default;
$Button-onDisabled-opacity: 0.65 !default;
@ -749,8 +768,10 @@ $Button-borderRadius: $borderRadius !default;
$Button--lg-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,
box-shadow 0.15s ease-in-out !default;
$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;
$Button--primary-bg: $primary !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-fontSize: $Form-input-fontSize !default;
$ColorPicker-paddingX: px2rem(12px) !default;
$ColorPicker-paddingY: ($ColorPicker-height - $ColorPicker-lineHeight * $ColorPicker-fontSize)/2 -
$ColorPicker-borderWidth !default;
$ColorPicker-paddingY: ($ColorPicker-height - $ColorPicker-lineHeight * $ColorPicker-fontSize)/2 - $ColorPicker-borderWidth !default;
$ColorPicker-placeholderColor: $Form-input-placeholderColor !default;
$ColorPicker-onFocused-borderColor: $Form-input-onFocused-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-lineHeight: $Button--sm-lineHeight !default;
$Combo-addBtn-paddingX: $Button--sm-paddingX !default;
$Combo-addBtn-paddingY: (
$Combo-addBtn-height - $Button-borderWidth * 2 - $Combo-addBtn-lineHeight * $Combo-addBtn-fontSize
)/2 !default;
$Combo-addBtn-paddingY: ($Combo-addBtn-height - $Button-borderWidth * 2 - $Combo-addBtn-lineHeight * $Combo-addBtn-fontSize)/2 !default;
$Combo--vertical-item-gap: px2rem(5px);
$Combo--vertical-item-borderColor: $borderColor !default;
@ -1080,9 +1098,7 @@ $SubForm--addBtn-borderRadius: $Button-borderRadius;
$SubForm--addBtn-height: $Button--sm-height !default;
$SubForm--addBtn-lineHeight: $Button--sm-lineHeight !default;
$SubForm--addBtn-paddingX: $Button--sm-paddingX !default;
$SubForm--addBtn-paddingY: (
$SubForm--addBtn-height - $Button-borderWidth * 2 - $SubForm--addBtn-lineHeight * $SubForm--addBtn-fontSize
)/2 !default;
$SubForm--addBtn-paddingY: ($SubForm--addBtn-height - $Button-borderWidth * 2 - $SubForm--addBtn-lineHeight * $SubForm--addBtn-fontSize)/2 !default;
// InputRange
$InputRange-fontFamily: $fontFamilyBase !default;
@ -1095,11 +1111,11 @@ $InputRange-onDisabled-color: #cccccc !default;
$InputRange-slider-bg: $InputRange-primaryColor !default;
$InputRange-slider-border: px2rem(1px) solid $InputRange-primaryColor !default;
$InputRange-slider-onFocus-borderRadius: $borderRadiusMd !default;
$InputRange-slider-onFocus-boxShadow: 0 0 0 $InputRange-slider-onFocus-borderRadius
transparentize($InputRange-slider-bg, 0.8) !default;
$InputRange-slider-onFocus-boxShadow: 0 0 0 $InputRange-slider-onFocus-borderRadius transparentize($InputRange-slider-bg, 0.8) !default;
$InputRange-slider-height: px2rem(24px) !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-slider-onActive-transform: scale(1.3) !default;
$InputRange-slider-onDisabled-bg: $InputRange-onDisabled-color !default;
@ -1115,10 +1131,26 @@ $InputRange-label--value-positionTop: px2rem(-40px) !default;
// input-range-track
$InputRange-track-bg: $InputRange-neutralLightColor !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-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
$TagControl-sugTip-color: $info !default;
@ -1138,10 +1170,7 @@ $TagControl-sugBtn-borderRadius: $Button-borderRadius !default;
$TagControl-sugBtn-height: $Button--sm-height !default;
$TagControl-sugBtn-lineHeight: $Button--sm-lineHeight !default;
$TagControl-sugBtn-paddingX: $Button--sm-paddingX !default;
$TagControl-sugBtn-paddingY: (
$TagControl-sugBtn-height - $Button-borderWidth * 2 - $TagControl-sugBtn-lineHeight *
$TagControl-sugBtn-fontSize
)/2 !default;
$TagControl-sugBtn-paddingY: ($TagControl-sugBtn-height - $Button-borderWidth * 2 - $TagControl-sugBtn-lineHeight * $TagControl-sugBtn-fontSize)/2 !default;
// Wizard
$Wizard-steps-bg: $gray100 !default;
@ -1312,4 +1341,4 @@ $Picker-iconColor: $gray600 !default;
$Picker-onHover-iconColor: darken($Picker-iconColor, 10%) !default;
$Picker-btn-vendor: 'FontAwesome' !default;
$Picker-btn-fontSize: $Form-fontSize !default;
$Picker-btn-icon: '\f2d2' !default;
$Picker-btn-icon: '\f2d2' !default;

View File

@ -1,9 +1,179 @@
// todo
.#{$ns}ImageControl {
outline: none;
&-addBtn {
margin: 0;
width: px2rem(120px);
height: px2rem(120px);
display: inline-flex;
justify-content: center;
align-items: center;
border: $borderWidth solid $borderColor;
cursor: pointer;
@include button-variant($ImageControl-addBtn-bg,
$ImageControl-addBtn-border,
$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;
}
}
&-item {
border: $borderWidth solid $borderColor;
vertical-align: top;
padding: px2rem(5px);
display: inline-block;
margin-right: px2rem(15px);
margin-right: px2rem(15px);
position: relative;
}
&-itemImageWrap {
width: px2rem(108px);
height: px2rem(108px);
overflow: hidden;
position: relative;
>img {
position: absolute;
left: 50%;
top: 50%;
height: 100%;
width: auto;
transform: translate(-50%, -50%);
}
}
&-itemOverlay {
background: rgba(0, 0, 0, 0.6);
position: absolute;
width: px2rem(108px);
height: px2rem(108px);
display: none;
top: px2rem(5px);
left: px2rem(5px);
justify-content: center;
align-items: center;
align-content: center;
flex-wrap: wrap;
color: #fff;
>div {
width: 100%;
text-align: center;
margin-bottom: 5px;
}
>a {
cursor: pointer;
color: #fff;
display: inline-block;
padding: 0 5px;
line-height: 1;
font-size: px2rem(16px);
}
}
&-item:hover &-itemOverlay {
display: flex;
}
&-itemClear {
position: absolute;
cursor: pointer;
color: #999;
top: 5px;
right: 5px;
line-height: 1;
>svg {
top: 0;
width: 10px;
height: 10px;
}
}
&-itemInfo {
display: inline-flex;
width: 110px;
height: 110px;
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;
}
}
}
// todo
.drop-zone {
border: $Form-input-borderWidth * 2 dashed $Form-input-borderColor;
height: 70px;
@ -14,7 +184,7 @@
position: relative;
cursor: pointer;
> div:not(.image-list) {
>div:not(.image-list) {
display: table-cell;
vertical-align: middle;
}
@ -99,8 +269,8 @@
align-content: center;
padding: 10px;
> a,
> button {
>a,
>button {
flex: 1;
outline: none;
display: flex;
@ -181,9 +351,10 @@
}
@media (min-width: 768px) {
.amis-image-control.form-contorl-inline,
.form-group-inline .amis-image-control {
display: inline-block;
min-width: 280px;
}
}
}

View File

@ -33,6 +33,12 @@ import PlusIcon from '../icons/plus.svg';
import MinusIcon from '../icons/minus.svg';
// @ts-ignore
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
@ -82,6 +88,9 @@ registerIcon('check', CheckIcon);
registerIcon('plus', PlusIcon);
registerIcon('minus', MinusIcon);
registerIcon('pencil', PencilIcon);
registerIcon('view', ViewIcon);
registerIcon('remove', RemoveIcon);
registerIcon('retry', RetryIcon);
export function 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

@ -1,17 +1,22 @@
import React from 'react';
import {FormItem, FormControlProps} from './Item';
import cx from 'classnames';
// @require 'cropperjs/dist/cropper.css';
import Cropper from 'react-cropper';
import DropZone from 'react-dropzone';
import 'blueimp-canvastoblob';
// @require 'cropperjs/dist/cropper.css';
// jest 不能支持这种写法
// import 'cropperjs/dist/cropper.css';
import find = require('lodash/find');
import qs from 'qs';
import {Payload} from '../../types';
import {filter} from '../../utils/tpl';
import {Switch} from '../../components';
import {buildApi} from '../../utils/api';
import {createObject, qsstringify} from '../../utils/helper';
import {Icon} from '../../components/icons';
let id = 1;
function gennerateId() {
return id++;
}
export interface ImageProps extends FormControlProps {
placeholder?: string;
@ -67,8 +72,10 @@ export interface FileValue {
}
export interface FileX extends File {
id?: string | number;
preview?: string;
state?: 'init' | 'error' | 'pending' | 'uploading' | 'uploaded' | 'invalid';
progress?: number;
[propName: string]: any;
}
@ -81,7 +88,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
btnClassName: 'btn-info btn-sm',
hideUploadButton: false,
compressOptions: {},
placeholder: '将图片拖入该区域,或者',
placeholder: '点击选择图片或者将图片拖入该区域',
joinValues: true,
extractValue: false,
delimiter: ',',
@ -106,7 +113,8 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
...(typeof value === 'string'
? {
value,
url: value
url: value,
id: gennerateId()
}
: value),
state: 'init'
@ -299,7 +307,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
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) {
this.current = file;
@ -309,44 +317,61 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
files: this.state.files.concat()
},
() =>
this.sendFile(file as FileX, (error, file, obj) => {
const files = this.state.files.concat();
const idx = files.indexOf(file);
this.sendFile(
file as FileX,
(error, file, obj) => {
const files = this.state.files.concat();
const idx = files.indexOf(file);
if (!~idx) {
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
);
if (!~idx) {
return;
}
} 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 {
this.setState(
@ -437,7 +462,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
}
handlePaste(e: React.ClipboardEvent<any>) {
const event = e.nativeEvent;
const event = e.nativeEvent as any;
const files: Array<FileX> = [];
const items = event.clipboardData.items;
@ -449,6 +474,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
}
blob.preview = window.URL.createObjectURL(blob);
blob.id = gennerateId();
files.push(blob);
});
@ -504,6 +530,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
}
file.state = 'pending';
file.id = gennerateId();
if (!file.preview || !file.url) {
file.preview = window.URL.createObjectURL(file);
}
@ -530,11 +557,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;
if (!limit) {
return this._upload(file, cb);
return this._upload(file, cb, onProgress);
}
const image = new Image();
@ -564,13 +595,17 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
file.state = 'invalid';
cb(error, file);
} else {
this._upload(file, cb);
this._upload(file, cb, onProgress);
}
};
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;
if (this.props.showCompressOptions) {
@ -581,7 +616,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) => {
if (ret.status) {
throw new Error(ret.msg || '上传失败, 请重试');
@ -598,29 +633,35 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
.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 data = this.props.data;
reciever = filter(reciever, data);
const api = buildApi(reciever, createObject(data, params), {
method: 'post'
});
const fileField = this.props.fileField || 'file';
fd.append(fileField, file, (file as File).name);
const idx = reciever.indexOf('?');
const idx = api.url.indexOf('?');
if (~idx && params) {
params = {
...qs.parse(reciever.substring(idx + 1)),
...params
};
reciever = reciever.substring(0, idx) + '?' + qs.stringify(params);
api.url = api.url.substring(0, idx) + '?' + qsstringify(params);
} else if (params) {
reciever += '?' + qs.stringify(params);
api.url += '?' + qsstringify(params);
}
// params && Object.keys(params).forEach(key => {
// const value = (params as any)[key];
// fd.append(key, value);
// });
if (api.data) {
qsstringify(api.data)
.split('&')
.forEach(item => {
let parts = item.split('=');
fd.append(parts[0], parts[1]);
});
}
const env = this.props.env;
@ -628,8 +669,9 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
throw new Error('fetcher is required');
}
return env.fetcher(reciever, fd, {
method: 'post'
return env.fetcher(api, fd, {
method: 'post',
onUploadProgress: (event: {loaded: number; total: number}) => onProgress(event.loaded / event.total)
});
}
@ -751,7 +793,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
render() {
const {
className,
classPrefix: ns,
classnames: cx,
placeholder,
disabled,
multiple,
@ -768,7 +810,7 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
const hasPending = files.some(file => file.state == 'pending');
return (
<div className={cx(`${ns}ImageControl`, className)} tabIndex={-1} onPaste={this.handlePaste}>
<div className={cx(`ImageControl`, className)} tabIndex={-1} onPaste={this.handlePaste}>
{cropFile ? (
<div className="cropper-wrapper">
<Cropper {...crop} ref="cropper" src={cropFile.preview} />
@ -782,11 +824,11 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
) : (
<DropZone
key="drop-zone"
className={cx('drop-zone', {
className={cx('ImageControl-dropzone', {
disabled,
'has-files': !!files.length
'is-empty': !files.length
})}
activeClassName="drop-zone-active"
activeClassName="is-active"
ref="dropzone"
onDrop={this.handleDrop}
onDropRejected={this.handleDropRejected}
@ -794,94 +836,114 @@ export default class ImageControl extends React.Component<ImageProps, ImageState
accept={accept}
multiple={multiple}
>
{files && files.length ? (
<div
className={cx('image-list clearfix', {
'image-list-multiple': multiple
})}
{files && files.length
? files.map((file, key) => (
<div
key={file.id || key}
className={cx('ImageControl-item', {
'is-uploaded': file.state !== 'uploading',
'is-invalid': file.state === 'error' || file.state == 'invalid'
})}
>
{file.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="bottom"
>
{files.map((file, key) => (
<div
key={key}
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>&times;</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>
)}
<Icon icon="plus" className="icon" />
</label>
) : null}
</DropZone>
)}