mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-02 03:58:07 +08:00
feat: input-image功能优化 (#6645)
This commit is contained in:
parent
a624a8d98a
commit
d2c52b0101
@ -449,6 +449,7 @@ app.listen(8080, function () {});
|
||||
| fixedSize | `boolean` | | 是否开启固定尺寸,若开启,需同时设置 fixedSizeClassName |
|
||||
| fixedSizeClassName | `string` | | 开启固定尺寸时,根据此值控制展示尺寸。例如`h-30`,即图片框高为 h-30,AMIS 将自动缩放比率设置默认图所占位置的宽度,最终上传图片根据此尺寸对应缩放。 |
|
||||
| initAutoFill | `boolean` | `false` | 表单反显时是否执行 autoFill |
|
||||
| uploadBtnText | `string` \| [SchemaNode](../../docs/types/schemanode) | | 上传按钮文案。支持tpl、schema形式配置。 |
|
||||
| dropCrop | `boolean` | `true` | 图片上传后是否进入裁剪模式 |
|
||||
| initCrop | `boolean` | `false` | 图片选择器初始化后是否立即进入裁剪模式 |
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
}
|
||||
|
||||
&-addBtn {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
width: px2rem(120px);
|
||||
height: px2rem(120px);
|
||||
@ -23,6 +24,20 @@
|
||||
var(--inputImage-base-default-bottom-left-border-radius);
|
||||
cursor: pointer;
|
||||
margin-right: var(--gap-base);
|
||||
|
||||
&-bg {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
.#{$ns}Image {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@include button-variant(
|
||||
var(--ImageControl-addBtn-bg),
|
||||
@ -88,6 +103,7 @@
|
||||
);
|
||||
|
||||
svg {
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
margin-bottom: var(--inputImage-base-default-icon-margin);
|
||||
font-size: var(--inputImage-base-default-icon-size);
|
||||
@ -97,11 +113,17 @@
|
||||
}
|
||||
|
||||
&-text {
|
||||
z-index: 1;
|
||||
font-size: var(--inputImage-base-default-fontSize);
|
||||
font-weight: var(--inputImage-base-default-fontWeight);
|
||||
}
|
||||
|
||||
&:not(:disabled):not(.is-disabled) {
|
||||
|
||||
&.is-invalid:hover {
|
||||
border-color: var(--FileControl-danger-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
color: var(--inputImage-base-hover-icon-color);
|
||||
@ -115,6 +137,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.is-invalid {
|
||||
border-color: var(--FileControl-danger-color);
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
pointer-events: none;
|
||||
|
||||
@ -145,7 +171,7 @@
|
||||
margin: 30px 0 0 var(--Tooltip--attr-gap);
|
||||
}
|
||||
|
||||
&-dropzone:focus &-addBtn {
|
||||
&-dropzone:focus &-addBtn:not(.is-invalid) {
|
||||
border-color: var(--inputImage-base-hover-top-border-color)
|
||||
var(--inputImage-base-hover-right-border-color)
|
||||
var(--inputImage-base-hover-bottom-border-color)
|
||||
@ -159,7 +185,7 @@
|
||||
}
|
||||
|
||||
&-dropzone:hover {
|
||||
&-addBtn:not(.is-disabled) {
|
||||
&-addBtn:not(.is-disabled):not(.is-invalid) {
|
||||
border-color: var(--inputImage-base-hover-top-border-color)
|
||||
var(--inputImage-base-hover-right-border-color)
|
||||
var(--inputImage-base-hover-bottom-border-color)
|
||||
@ -173,7 +199,7 @@
|
||||
}
|
||||
|
||||
&-item {
|
||||
// border: var(--borderWidth) solid var(--borderColor);
|
||||
border: var(--borderWidth) solid var(--borderColor);
|
||||
border-radius: var(--ImageControl-addBtn-borderRadius);
|
||||
vertical-align: top;
|
||||
padding: var(--gap-xs);
|
||||
@ -188,6 +214,10 @@
|
||||
border-color: var(--FileControl-danger-color);
|
||||
}
|
||||
|
||||
&-errorTip {
|
||||
color: var(--FileControl-danger-color);
|
||||
}
|
||||
|
||||
svg.icon-refresh {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import cx from 'classnames';
|
||||
import {ClassNamesFn, themeable} from 'amis-core';
|
||||
|
||||
interface TooltipProps extends React.HTMLProps<HTMLDivElement> {
|
||||
@ -19,6 +18,7 @@ interface TooltipProps extends React.HTMLProps<HTMLDivElement> {
|
||||
placement?: string;
|
||||
showArrow?: boolean;
|
||||
tooltipTheme?: string;
|
||||
bodyClassName?: string;
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
@ -48,6 +48,7 @@ export class Tooltip extends React.Component<TooltipProps> {
|
||||
showArrow,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
bodyClassName,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
@ -69,7 +70,7 @@ export class Tooltip extends React.Component<TooltipProps> {
|
||||
<div className={cx(`Tooltip-arrow`)} {...arrowProps} />
|
||||
) : null}
|
||||
{title ? <div className={cx('Tooltip-title')}>{title}</div> : null}
|
||||
<div className={cx('Tooltip-body')}>{children}</div>
|
||||
<div className={cx(bodyClassName, 'Tooltip-body')}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -80,6 +80,11 @@ export interface TooltipObject {
|
||||
* 文字提示浮层CSS类名
|
||||
*/
|
||||
tooltipClassName?: string;
|
||||
|
||||
/**
|
||||
* 文字提示浮层Body的CSS类名
|
||||
*/
|
||||
tooltipBodyClassName?: string;
|
||||
/**
|
||||
* html xss filter
|
||||
*/
|
||||
@ -299,6 +304,7 @@ export class TooltipWrapper extends React.Component<
|
||||
trigger,
|
||||
rootClose,
|
||||
tooltipClassName,
|
||||
tooltipBodyClassName,
|
||||
style,
|
||||
disabled = false,
|
||||
offset,
|
||||
@ -347,6 +353,7 @@ export class TooltipWrapper extends React.Component<
|
||||
className={tooltipClassName}
|
||||
tooltipTheme={tooltipTheme}
|
||||
showArrow={showArrow}
|
||||
bodyClassName={tooltipBodyClassName}
|
||||
onMouseEnter={
|
||||
~triggers.indexOf('hover') ? this.tooltipMouseEnter : () => {}
|
||||
}
|
||||
|
@ -141,7 +141,6 @@ register('de-DE', {
|
||||
'File.sizeLimit': 'Die maximale Dateigröße ist {{maxSize}}',
|
||||
'File.start': 'Hochladen beginnen',
|
||||
'File.upload': 'Hochladen',
|
||||
'Image.upload': 'Hochladen',
|
||||
'File.uploadFailed': 'Zurückgegebene Daten der Upload-API sind leer',
|
||||
'File.uploading': 'Wird hochgeladen...',
|
||||
'FormItem.autoFillLoadFailed':
|
||||
@ -157,6 +156,7 @@ register('de-DE', {
|
||||
'Iframe.invalid': 'Ungültige Iframe-URL',
|
||||
'Iframe.invalidProtocol':
|
||||
'HTTP-URL-Iframe kann nicht in https verwendet werden',
|
||||
'Image.upload': 'Bild hochladen',
|
||||
'Image.configError':
|
||||
'Es können nur eine Beschneidung oder mehrere festgelegt werden',
|
||||
'Image.crop': 'Bild beschneiden',
|
||||
@ -241,7 +241,8 @@ register('de-DE', {
|
||||
'Table.valueField': 'valueField muss vorhanden sein',
|
||||
'Table.index': 'Index',
|
||||
'Table.add': 'Neu',
|
||||
'Table.addButtonDisabledTip': 'Reichen Sie bei der Inhaltsbearbeitung zuerst ein und erstellen Sie dann eine neue Option',
|
||||
'Table.addButtonDisabledTip':
|
||||
'Reichen Sie bei der Inhaltsbearbeitung zuerst ein und erstellen Sie dann eine neue Option',
|
||||
'Table.toggleColumn': 'Spalten anzeigen',
|
||||
'Table.searchFields': 'Abfragefelder setzen',
|
||||
'Tag.placeholder': 'Noch kein Tag',
|
||||
|
@ -139,7 +139,6 @@ register('en-US', {
|
||||
'File.sizeLimit': 'The maximum file size is {{maxSize}}',
|
||||
'File.start': 'Start upload',
|
||||
'File.upload': 'Upload',
|
||||
'Image.upload': 'Upload image',
|
||||
'File.uploadFailed': 'return data of udpload api is empty',
|
||||
'File.uploading': 'Uploading',
|
||||
'FormItem.autoFillLoadFailed': 'return data of autoUpdate api is error',
|
||||
@ -152,6 +151,8 @@ register('en-US', {
|
||||
'Form.nestedError': 'Form cannot appear as a descendant of form',
|
||||
'Iframe.invalid': 'Invalid iframe url',
|
||||
'Iframe.invalidProtocol': 'Can not use http url iframe in https',
|
||||
'Image.upload': 'Upload image',
|
||||
'Image.errorRetry': 'upload failed, please try again',
|
||||
'Image.configError': 'Can only set one of crop or multiple',
|
||||
'Image.crop': 'Crop image',
|
||||
'Image.dragDrop': `Drag 'n' drop some photos here`,
|
||||
@ -233,7 +234,8 @@ register('en-US', {
|
||||
'Table.valueField': 'Must have valueField',
|
||||
'Table.index': 'Index',
|
||||
'Table.add': 'Add',
|
||||
'Table.addButtonDisabledTip': 'In content editing, please submit first and then create a new option',
|
||||
'Table.addButtonDisabledTip':
|
||||
'In content editing, please submit first and then create a new option',
|
||||
'Table.toggleColumn': 'Display columns',
|
||||
'Table.searchFields': 'Set query fields',
|
||||
'Tag.placeholder': 'No tag yet',
|
||||
|
@ -130,7 +130,7 @@ register('zh-CN', {
|
||||
'File.dragDrop': '将文件拖到此处,或',
|
||||
'File.clickUpload': '点击上传',
|
||||
'File.helpText': '帮助文档',
|
||||
'File.errorRetry': '文件上传失败请重试',
|
||||
'File.errorRetry': '文件上传失败,请重试',
|
||||
'File.failed': '失败文件',
|
||||
'File.invalidType': '{{files}} 不符合类型的 {{accept}} 的设定,请仔细检查',
|
||||
'File.maxLength': '最多上传 {{maxLength}} 个文件',
|
||||
@ -143,7 +143,6 @@ register('zh-CN', {
|
||||
'File.sizeLimit': '文件大小不超过 {{maxSize}}',
|
||||
'File.start': '开始上传',
|
||||
'File.upload': '文件上传',
|
||||
'Image.upload': '图片上传',
|
||||
'File.uploadFailed': '接口返回错误,请仔细检查',
|
||||
'File.uploading': '上传中...',
|
||||
'FormItem.autoFillLoadFailed': '接口返回错误,请仔细检查',
|
||||
@ -156,6 +155,8 @@ register('zh-CN', {
|
||||
'Form.nestedError': '表单不要直接嵌套在表单下面',
|
||||
'Iframe.invalid': 'iframe 地址不合法',
|
||||
'Iframe.invalidProtocol': '无法加载 http 协议的 iframe',
|
||||
'Image.upload': '图片上传',
|
||||
'Image.errorRetry': '上传失败,请重试',
|
||||
'Image.configError': '图片多选配置和裁剪配置只能设置一个',
|
||||
'Image.crop': '裁剪图片',
|
||||
'Image.dragDrop': '将图片拖拽到此处',
|
||||
|
@ -10,14 +10,13 @@ import {
|
||||
// import 'cropperjs/dist/cropper.css';
|
||||
const Cropper = React.lazy(() => import('react-cropper'));
|
||||
import DropZone from 'react-dropzone';
|
||||
import {FileRejection, DropEvent} from 'react-dropzone';
|
||||
import {FileRejection, ErrorCode, DropEvent} from 'react-dropzone';
|
||||
import 'blueimp-canvastoblob';
|
||||
import find from 'lodash/find';
|
||||
import {Payload, ActionObject} from 'amis-core';
|
||||
import {buildApi} from 'amis-core';
|
||||
import {createObject, qsstringify, guid, isEmpty, qsparse} from 'amis-core';
|
||||
import {Icon} from 'amis-ui';
|
||||
import {Button} from 'amis-ui';
|
||||
import {Icon, TooltipWrapper, Button} from 'amis-ui';
|
||||
import accepts from 'attr-accept';
|
||||
import {getNameFromUrl} from './InputFile';
|
||||
import ImageComponent, {ImageThumbProps} from '../Image';
|
||||
@ -34,6 +33,7 @@ import {filter} from 'amis-core';
|
||||
import isPlainObject from 'lodash/isPlainObject';
|
||||
import merge from 'lodash/merge';
|
||||
import omit from 'lodash/omit';
|
||||
import {TplSchema} from '../Tpl';
|
||||
|
||||
/**
|
||||
* Image 图片上传控件
|
||||
@ -75,6 +75,11 @@ export interface ImageControlSchema extends FormBaseControlSchema {
|
||||
*/
|
||||
autoUpload?: boolean;
|
||||
|
||||
/**
|
||||
* 上传按钮文案
|
||||
*/
|
||||
uploadBtnText?: string | TplSchema;
|
||||
|
||||
/**
|
||||
* 选择图片按钮的 CSS 类名
|
||||
*/
|
||||
@ -304,6 +309,8 @@ export interface ImageState {
|
||||
cropFileName?: string; // 主要是用于后续上传的时候获得用户名
|
||||
submitOnChange?: boolean;
|
||||
frameImageWidth?: number;
|
||||
// drop-zone 组件是否能多选的限制,主要是为了限制重选情况下只能单选,其他情况和 props 的 multiple 一致
|
||||
dropMultiple?: boolean;
|
||||
}
|
||||
|
||||
export interface FileValue {
|
||||
@ -324,6 +331,7 @@ export interface FileX extends File {
|
||||
preview?: string;
|
||||
state?: 'init' | 'error' | 'pending' | 'uploading' | 'uploaded' | 'invalid';
|
||||
progress?: number;
|
||||
error?: string;
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
@ -384,7 +392,8 @@ export default class ImageControl extends React.Component<
|
||||
state: ImageState = {
|
||||
uploading: false,
|
||||
locked: false,
|
||||
files: []
|
||||
files: [],
|
||||
dropMultiple: false
|
||||
};
|
||||
|
||||
files: Array<FileValue | FileX> = [];
|
||||
@ -429,6 +438,7 @@ export default class ImageControl extends React.Component<
|
||||
...this.state,
|
||||
files: (this.files = files),
|
||||
crop: this.buildCrop(props),
|
||||
dropMultiple: props.multiple,
|
||||
frameImageWidth: 0
|
||||
};
|
||||
|
||||
@ -453,6 +463,7 @@ export default class ImageControl extends React.Component<
|
||||
this.handlePaste = this.handlePaste.bind(this);
|
||||
this.syncAutoFill = this.syncAutoFill.bind(this);
|
||||
this.handleReSelect = this.handleReSelect.bind(this);
|
||||
this.handleFileCancel = this.handleFileCancel.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -518,6 +529,12 @@ export default class ImageControl extends React.Component<
|
||||
);
|
||||
}
|
||||
|
||||
if (prevProps.multiple !== props.multiple) {
|
||||
this.setState({
|
||||
dropMultiple: props.multiple
|
||||
});
|
||||
}
|
||||
|
||||
if (prevProps.crop !== props.crop) {
|
||||
this.setState({
|
||||
crop: this.buildCrop(props)
|
||||
@ -564,31 +581,91 @@ export default class ImageControl extends React.Component<
|
||||
if (evt.type !== 'change' && evt.type !== 'drop') {
|
||||
return;
|
||||
}
|
||||
const {multiple, env, accept, translate: __} = this.props;
|
||||
|
||||
const files = rejectedFiles.map(fileRejection => ({
|
||||
...fileRejection.file,
|
||||
state: 'invalid',
|
||||
id: guid(),
|
||||
name: fileRejection.file.name
|
||||
}));
|
||||
const {
|
||||
accept,
|
||||
multiple,
|
||||
formItem,
|
||||
maxLength,
|
||||
maxSize,
|
||||
translate: __
|
||||
} = this.props;
|
||||
|
||||
// this.setState({
|
||||
// files: this.files = multiple
|
||||
// ? this.files.concat(files)
|
||||
// : this.files.length
|
||||
// ? this.files
|
||||
// : files.slice(0, 1)
|
||||
// });
|
||||
let reFiles = rejectedFiles.map(item => item.file);
|
||||
let currentFiles = this.files;
|
||||
|
||||
env.alert(
|
||||
__('File.invalidType', {
|
||||
files: files.map((file: any) => `「${file.name}」`).join(' '),
|
||||
accept
|
||||
})
|
||||
if (!multiple && currentFiles.length) {
|
||||
currentFiles = [];
|
||||
}
|
||||
|
||||
const allowed =
|
||||
(multiple
|
||||
? maxLength
|
||||
? maxLength
|
||||
: reFiles.length + currentFiles.length
|
||||
: 1) - currentFiles.length;
|
||||
|
||||
// 限制过多的错误文件
|
||||
if (allowed <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const errorFiles = [].slice.call(reFiles, 0, allowed);
|
||||
|
||||
const formatFile = (file: FileX): FileX => {
|
||||
file.id = guid();
|
||||
const errors = rejectedFiles.find(i => i.file === file)?.errors;
|
||||
if (errors) {
|
||||
file.error = errors
|
||||
.map(err => {
|
||||
// 类型错误
|
||||
if (err.code === ErrorCode.FileInvalidType) {
|
||||
return __('File.invalidType', {
|
||||
files: file.name,
|
||||
accept
|
||||
});
|
||||
}
|
||||
// 文件太大
|
||||
else if (err.code === ErrorCode.FileTooLarge) {
|
||||
return __('File.sizeLimit', {maxSize});
|
||||
}
|
||||
})
|
||||
.join('; ');
|
||||
}
|
||||
file.state = 'invalid';
|
||||
return file;
|
||||
};
|
||||
|
||||
if (multiple) {
|
||||
if (this.reuploadIndex !== undefined) {
|
||||
currentFiles.splice(this.reuploadIndex, 1, formatFile(errorFiles[0]));
|
||||
this.reuploadIndex = undefined;
|
||||
} else {
|
||||
errorFiles.forEach((item: any) => {
|
||||
currentFiles.push(formatFile(item));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const file = formatFile(errorFiles[0]);
|
||||
currentFiles.splice(0, 1, file);
|
||||
formItem?.setError(file?.error ?? '');
|
||||
}
|
||||
|
||||
return this.setState(
|
||||
{
|
||||
files: (this.files = currentFiles),
|
||||
dropMultiple: multiple
|
||||
},
|
||||
this.tick
|
||||
);
|
||||
}
|
||||
|
||||
handleFileCancel() {
|
||||
this.setState({
|
||||
dropMultiple: this.props.multiple
|
||||
});
|
||||
}
|
||||
|
||||
startUpload(retry: boolean = false) {
|
||||
if (this.state.uploading) {
|
||||
return;
|
||||
@ -630,8 +707,7 @@ export default class ImageControl extends React.Component<
|
||||
return;
|
||||
}
|
||||
|
||||
const env = this.props.env;
|
||||
const __ = this.props.translate;
|
||||
const {translate: __, formItem, multiple} = this.props;
|
||||
const file = find(this.files, item => item.state === 'pending') as FileX;
|
||||
if (file) {
|
||||
this.current = file;
|
||||
@ -658,21 +734,12 @@ export default class ImageControl extends React.Component<
|
||||
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: (this.files = files),
|
||||
error: error
|
||||
},
|
||||
this.tick
|
||||
);
|
||||
if (!multiple) {
|
||||
formItem?.setError(error);
|
||||
if (files.length === 1) {
|
||||
files.splice(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
env.notify('error', error || __('File.errorRetry'));
|
||||
} else {
|
||||
newFile = {
|
||||
name: file.name || this.state.cropFileName,
|
||||
@ -682,7 +749,7 @@ export default class ImageControl extends React.Component<
|
||||
}
|
||||
files.splice(idx, 1, newFile);
|
||||
this.current = null;
|
||||
this.setState(
|
||||
return this.setState(
|
||||
{
|
||||
files: (this.files = files)
|
||||
},
|
||||
@ -713,11 +780,10 @@ export default class ImageControl extends React.Component<
|
||||
},
|
||||
async () => {
|
||||
await this.onChange(!!this.resolve, false);
|
||||
|
||||
if (this.resolve) {
|
||||
this.resolve(
|
||||
this.files.some(file => file.state === 'error')
|
||||
? __('File.errorRetry')
|
||||
? __('Image.errorRetry')
|
||||
: null
|
||||
);
|
||||
this.resolve = undefined;
|
||||
@ -831,7 +897,7 @@ export default class ImageControl extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
onChange((this.emitValue = newValue || ''), undefined, changeImmediately);
|
||||
onChange(this.emitValue, undefined, changeImmediately);
|
||||
curInitAutoFill && this.syncAutoFill();
|
||||
}
|
||||
|
||||
@ -910,7 +976,12 @@ export default class ImageControl extends React.Component<
|
||||
if (event && event.type === 'drop' && this.reuploadIndex !== undefined) {
|
||||
this.reuploadIndex = undefined;
|
||||
}
|
||||
this.addFiles(files);
|
||||
this.setState(
|
||||
{
|
||||
dropMultiple: multiple
|
||||
},
|
||||
() => this.addFiles(files)
|
||||
);
|
||||
}
|
||||
|
||||
handlePaste(e: React.ClipboardEvent<any>) {
|
||||
@ -976,7 +1047,7 @@ export default class ImageControl extends React.Component<
|
||||
return;
|
||||
}
|
||||
|
||||
const {multiple, maxLength, maxSize, accept, translate: __} = this.props;
|
||||
const {multiple, maxLength, maxSize, translate: __} = this.props;
|
||||
let currentFiles = this.files;
|
||||
|
||||
if (!multiple && currentFiles.length) {
|
||||
@ -1115,7 +1186,7 @@ export default class ImageControl extends React.Component<
|
||||
this._send(file, this.props.receiver as string, {}, onProgress)
|
||||
.then(async (ret: Payload) => {
|
||||
if (ret.status && (ret as any).status !== '0') {
|
||||
throw new Error(ret.msg || __('File.errorRetry'));
|
||||
throw new Error(ret.msg || __('Image.errorRetry'));
|
||||
}
|
||||
|
||||
const obj: FileValue = {
|
||||
@ -1143,7 +1214,7 @@ export default class ImageControl extends React.Component<
|
||||
if (dispatcher?.prevented) {
|
||||
return;
|
||||
}
|
||||
cb(error.message || __('File.errorRetry'), file);
|
||||
cb(error.message || __('Image.errorRetry'), file);
|
||||
});
|
||||
}
|
||||
|
||||
@ -1280,7 +1351,7 @@ export default class ImageControl extends React.Component<
|
||||
}
|
||||
|
||||
validate(): any {
|
||||
const __ = this.props.translate;
|
||||
const {translate: __, multiple} = this.props;
|
||||
|
||||
if (this.state.locked && this.state.lockedReason) {
|
||||
return this.state.lockedReason;
|
||||
@ -1297,8 +1368,10 @@ export default class ImageControl extends React.Component<
|
||||
this.resolve = resolve;
|
||||
this.startUpload();
|
||||
});
|
||||
} else if (this.files.some(item => item.state === 'error')) {
|
||||
return __('File.errorRetry');
|
||||
} else if (
|
||||
this.files.some(i => i.state && ['error', 'invalid'].includes(i.state))
|
||||
) {
|
||||
return multiple ? ' ' : this.files[0].error;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1335,16 +1408,20 @@ export default class ImageControl extends React.Component<
|
||||
// 重新上传
|
||||
handleReSelect(index: number) {
|
||||
this.reuploadIndex = index;
|
||||
this.dropzone.current && this.dropzone.current.open();
|
||||
this.setState(
|
||||
{
|
||||
dropMultiple: false
|
||||
},
|
||||
() => {
|
||||
this.dropzone.current && this.dropzone.current.open();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
style,
|
||||
classnames: cx,
|
||||
placeholder,
|
||||
placeholderPlacement,
|
||||
disabled,
|
||||
multiple,
|
||||
accept,
|
||||
@ -1357,6 +1434,9 @@ export default class ImageControl extends React.Component<
|
||||
frameImage,
|
||||
fixedSize,
|
||||
fixedSizeClassName,
|
||||
uploadBtnText,
|
||||
maxSize,
|
||||
render,
|
||||
themeCss,
|
||||
inputImageControlClassName,
|
||||
addBtnControlClassName,
|
||||
@ -1414,8 +1494,15 @@ export default class ImageControl extends React.Component<
|
||||
null
|
||||
);
|
||||
|
||||
const {files, error, crop, uploading, cropFile, frameImageWidth} =
|
||||
this.state;
|
||||
const {
|
||||
files,
|
||||
error,
|
||||
crop,
|
||||
uploading,
|
||||
cropFile,
|
||||
frameImageWidth,
|
||||
dropMultiple
|
||||
} = this.state;
|
||||
let frameImageStyle: any = {};
|
||||
if (fixedSizeClassName && frameImageWidth && fixedSize) {
|
||||
frameImageStyle.width = frameImageWidth;
|
||||
@ -1472,17 +1559,18 @@ export default class ImageControl extends React.Component<
|
||||
ref={this.dropzone}
|
||||
onDrop={this.handleDrop}
|
||||
onDropRejected={this.handleDropRejected}
|
||||
onFileDialogCancel={this.handleFileCancel}
|
||||
accept={accept}
|
||||
multiple={multiple}
|
||||
multiple={dropMultiple}
|
||||
disabled={disabled}
|
||||
maxSize={maxSize}
|
||||
>
|
||||
{({
|
||||
getRootProps,
|
||||
getInputProps,
|
||||
isDragActive,
|
||||
isDragAccept,
|
||||
isDragReject,
|
||||
isFocused
|
||||
isDragReject
|
||||
}) => (
|
||||
<div
|
||||
{...getRootProps({
|
||||
@ -1527,51 +1615,71 @@ export default class ImageControl extends React.Component<
|
||||
>
|
||||
{file.state === 'invalid' ||
|
||||
file.state === 'error' ? (
|
||||
<div className={cx('Image--thumb')}>
|
||||
<div className={cx('Image-thumbWrap')}>
|
||||
<div
|
||||
className={cx(
|
||||
'Image-thumb',
|
||||
'ImageControl-filename'
|
||||
)}
|
||||
>
|
||||
<Icon icon="image" className="icon" />
|
||||
<span
|
||||
title={
|
||||
file.name ||
|
||||
getNameFromUrl(file.value || file.url)
|
||||
}
|
||||
<TooltipWrapper
|
||||
placement="top"
|
||||
tooltip={{
|
||||
content: file.error,
|
||||
disabled: !multiple && files.length === 1,
|
||||
tooltipBodyClassName: cx(
|
||||
'ImageControl-item-errorTip'
|
||||
)
|
||||
}}
|
||||
trigger="hover"
|
||||
>
|
||||
<div className={cx('Image--thumb')}>
|
||||
<div className={cx('Image-thumbWrap')}>
|
||||
<div
|
||||
className={cx(
|
||||
'Image-thumb',
|
||||
'ImageControl-filename'
|
||||
)}
|
||||
>
|
||||
{file.name ||
|
||||
getNameFromUrl(file.value || file.url)}
|
||||
</span>
|
||||
</div>
|
||||
<Icon icon="image" className="icon" />
|
||||
<span
|
||||
title={
|
||||
file.name ||
|
||||
getNameFromUrl(file.value || file.url)
|
||||
}
|
||||
>
|
||||
{file.name ||
|
||||
getNameFromUrl(
|
||||
file.value || file.url
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className={cx('Image-overlay')}>
|
||||
<a
|
||||
data-tooltip={__('File.repick')}
|
||||
data-position="bottom"
|
||||
onClick={this.handleRetry.bind(this, key)}
|
||||
>
|
||||
<Icon icon="upload" className="icon" />
|
||||
</a>
|
||||
|
||||
{!disabled ? (
|
||||
<div className={cx('Image-overlay')}>
|
||||
<a
|
||||
data-tooltip={__('Select.clear')}
|
||||
data-tooltip={__('File.repick')}
|
||||
data-position="bottom"
|
||||
onClick={this.removeFile.bind(
|
||||
onClick={this.handleReSelect.bind(
|
||||
this,
|
||||
file,
|
||||
key
|
||||
)}
|
||||
>
|
||||
<Icon icon="remove" className="icon" />
|
||||
<Icon icon="upload" className="icon" />
|
||||
</a>
|
||||
) : null}
|
||||
|
||||
{!disabled ? (
|
||||
<a
|
||||
data-tooltip={__('Select.clear')}
|
||||
data-position="bottom"
|
||||
onClick={this.removeFile.bind(
|
||||
this,
|
||||
file,
|
||||
key
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
icon="remove"
|
||||
className="icon"
|
||||
/>
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TooltipWrapper>
|
||||
) : file.state === 'uploading' ? (
|
||||
<>
|
||||
<a
|
||||
@ -1627,24 +1735,6 @@ export default class ImageControl extends React.Component<
|
||||
thumbRatio={thumbRatio}
|
||||
overlays={
|
||||
<>
|
||||
{/* {file.info ? (
|
||||
[
|
||||
<div key="info">
|
||||
{file.info.width} x{' '}
|
||||
{file.info.height}
|
||||
</div>,
|
||||
file.info.len ? (
|
||||
<div key="size">
|
||||
{prettyBytes(
|
||||
file.info.len
|
||||
, 1024)}
|
||||
</div>
|
||||
) : null
|
||||
]
|
||||
) : (
|
||||
<div>...</div>
|
||||
)} */}
|
||||
|
||||
<a
|
||||
data-tooltip={__('Image.zoomIn')}
|
||||
data-position="bottom"
|
||||
@ -1730,55 +1820,58 @@ export default class ImageControl extends React.Component<
|
||||
|
||||
{(multiple && (!maxLength || files.length < maxLength)) ||
|
||||
(!multiple && !files.length) ? (
|
||||
<label
|
||||
className={cx(
|
||||
'ImageControl-addBtn',
|
||||
{
|
||||
'is-disabled': disabled
|
||||
},
|
||||
fixedSize ? 'ImageControl-fixed-size' : '',
|
||||
fixedSize ? fixedSizeClassName : '',
|
||||
addBtnControlClassName
|
||||
)}
|
||||
style={frameImageStyle}
|
||||
onClick={this.handleSelect}
|
||||
data-tooltip={__(placeholder)}
|
||||
data-position={placeholderPlacement}
|
||||
ref={this.frameImageRef}
|
||||
<TooltipWrapper
|
||||
placement="top"
|
||||
trigger="hover"
|
||||
tooltip={{
|
||||
content: error,
|
||||
disabled: !multiple || !error
|
||||
}}
|
||||
>
|
||||
{filterFrameImage ? (
|
||||
<ImageComponent
|
||||
key="upload-default-image"
|
||||
src={filterFrameImage}
|
||||
className={cx(
|
||||
fixedSize ? 'Image-thumb--fixed-size' : ''
|
||||
<label
|
||||
className={cx(
|
||||
'ImageControl-addBtn',
|
||||
{
|
||||
'is-disabled': disabled
|
||||
},
|
||||
fixedSize ? 'ImageControl-fixed-size' : '',
|
||||
fixedSize ? fixedSizeClassName : '',
|
||||
addBtnControlClassName,
|
||||
error ? 'is-invalid' : ''
|
||||
)}
|
||||
style={frameImageStyle}
|
||||
onClick={this.handleSelect}
|
||||
ref={this.frameImageRef}
|
||||
>
|
||||
<Icon
|
||||
icon="plus-fine"
|
||||
className="icon"
|
||||
iconContent={cx(
|
||||
':ImageControl-addBtn-icon',
|
||||
iconControlClassName
|
||||
)}
|
||||
onLoad={this.handleFrameImageLoaded.bind(this)}
|
||||
thumbMode={thumbMode}
|
||||
thumbRatio={thumbRatio}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Icon
|
||||
icon="plus-fine"
|
||||
className="icon"
|
||||
iconContent={cx(
|
||||
':ImageControl-addBtn-icon',
|
||||
iconControlClassName
|
||||
)}
|
||||
/>
|
||||
<span className={cx('ImageControl-addBtn-text')}>
|
||||
{__('Image.upload')}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isFocused ? (
|
||||
<span className={cx('ImageControl-pasteTip')}>
|
||||
{__('Image.pasteTip')}
|
||||
<span className={cx('ImageControl-addBtn-text')}>
|
||||
{!uploadBtnText
|
||||
? __('Image.upload')
|
||||
: render(`btn-upload-text`, uploadBtnText, {})}
|
||||
</span>
|
||||
) : null}
|
||||
</label>
|
||||
{filterFrameImage ? (
|
||||
<div className={cx('ImageControl-addBtn-bg')}>
|
||||
<ImageComponent
|
||||
key="upload-default-image"
|
||||
src={filterFrameImage}
|
||||
className={cx(
|
||||
fixedSize ? 'Image-thumb--fixed-size' : ''
|
||||
)}
|
||||
onLoad={this.handleFrameImageLoaded.bind(this)}
|
||||
thumbMode={thumbMode}
|
||||
thumbRatio={thumbRatio}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</label>
|
||||
</TooltipWrapper>
|
||||
) : null}
|
||||
|
||||
{!autoUpload && !hideUploadButton && files.length ? (
|
||||
@ -1791,10 +1884,6 @@ export default class ImageControl extends React.Component<
|
||||
{__(uploading ? 'File.pause' : 'File.start')}
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{error ? (
|
||||
<div className={cx('ImageControl-errorMsg')}>{error}</div>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user