mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 02:58:05 +08:00
parent
c73239b60b
commit
20212a4d28
@ -420,38 +420,67 @@ app.listen(8080, function () {});
|
||||
}
|
||||
```
|
||||
|
||||
## 拖拽排序
|
||||
|
||||
可配置 `draggable` 为 `true` 启动拖拽排序。
|
||||
|
||||
```schema: scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"title": "表单",
|
||||
data: {
|
||||
image: [
|
||||
'http://www.sortablejs.com/assets/img/npm.png',
|
||||
'http://www.sortablejs.com/assets/img/bower.png',
|
||||
'http://www.sortablejs.com/assets/img/js.png'
|
||||
]
|
||||
},
|
||||
"body": [
|
||||
'Images: <br />${image|split|join:"<br />"}',
|
||||
{
|
||||
type: 'input-image',
|
||||
name: 'image',
|
||||
multiple: true,
|
||||
draggable: true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 属性表
|
||||
|
||||
除了支持 [普通表单项属性表](./formitem#%E5%B1%9E%E6%80%A7%E8%A1%A8) 中的配置以外,还支持下面一些配置
|
||||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------------------ | ------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| receiver | [API](../../../docs/types/api) | | 上传文件接口 |
|
||||
| accept | `string` | `.jpeg,.jpg,.png,.gif` | 支持的图片类型格式,请配置此属性为图片后缀,例如`.jpg,.png` |
|
||||
| maxSize | `number` | | 默认没有限制,当设置后,文件大小大于此值将不允许上传。单位为`B` |
|
||||
| maxLength | `number` | | 默认没有限制,当设置后,一次只允许上传指定数量文件。 |
|
||||
| multiple | `boolean` | `false` | 是否多选。 |
|
||||
| joinValues | `boolean` | `true` | [拼接值](./options#%E6%8B%BC%E6%8E%A5%E5%80%BC-joinvalues) |
|
||||
| extractValue | `boolean` | `false` | [提取值](./options#%E6%8F%90%E5%8F%96%E5%A4%9A%E9%80%89%E5%80%BC-extractvalue) |
|
||||
| delimiter | `string` | `,` | [拼接符](./options#%E6%8B%BC%E6%8E%A5%E7%AC%A6-delimiter) |
|
||||
| autoUpload | `boolean` | `true` | 否选择完就自动开始上传 |
|
||||
| hideUploadButton | `boolean` | `false` | 隐藏上传按钮 |
|
||||
| fileField | `string` | `file` | 如果你不想自己存储,则可以忽略此属性。 |
|
||||
| crop | `boolean`或`{"aspectRatio":""}` | | 用来设置是否支持裁剪。 |
|
||||
| crop.aspectRatio | `number` | | 裁剪比例。浮点型,默认 `1` 即 `1:1`,如果要设置 `16:9` 请设置 `1.7777777777777777` 即 `16 / 9`。。 |
|
||||
| crop.rotatable | `boolean` | `false` | 裁剪时是否可旋转 |
|
||||
| crop.scalable | `boolean` | `false` | 裁剪时是否可缩放 |
|
||||
| crop.viewMode | `number` | `1` | 裁剪时的查看模式,0 是无限制 |
|
||||
| cropFormat | `string` | `image/png` | 裁剪文件格式 |
|
||||
| cropQuality | `number` | `1` | 裁剪文件格式的质量,用于 jpeg/webp,取值在 0 和 1 之间 |
|
||||
| limit | Limit | | 限制图片大小,超出不让上传。 |
|
||||
| frameImage | `string` | | 默认占位图地址 |
|
||||
| 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` | 图片选择器初始化后是否立即进入裁剪模式 |
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ------------------ | ----------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| receiver | [API](../../../docs/types/api) | | 上传文件接口 |
|
||||
| accept | `string` | `.jpeg,.jpg,.png,.gif` | 支持的图片类型格式,请配置此属性为图片后缀,例如`.jpg,.png` |
|
||||
| maxSize | `number` | | 默认没有限制,当设置后,文件大小大于此值将不允许上传。单位为`B` |
|
||||
| maxLength | `number` | | 默认没有限制,当设置后,一次只允许上传指定数量文件。 |
|
||||
| multiple | `boolean` | `false` | 是否多选。 |
|
||||
| joinValues | `boolean` | `true` | [拼接值](./options#%E6%8B%BC%E6%8E%A5%E5%80%BC-joinvalues) |
|
||||
| extractValue | `boolean` | `false` | [提取值](./options#%E6%8F%90%E5%8F%96%E5%A4%9A%E9%80%89%E5%80%BC-extractvalue) |
|
||||
| delimiter | `string` | `,` | [拼接符](./options#%E6%8B%BC%E6%8E%A5%E7%AC%A6-delimiter) |
|
||||
| autoUpload | `boolean` | `true` | 否选择完就自动开始上传 |
|
||||
| hideUploadButton | `boolean` | `false` | 隐藏上传按钮 |
|
||||
| fileField | `string` | `file` | 如果你不想自己存储,则可以忽略此属性。 |
|
||||
| crop | `boolean`或`{"aspectRatio":""}` | | 用来设置是否支持裁剪。 |
|
||||
| crop.aspectRatio | `number` | | 裁剪比例。浮点型,默认 `1` 即 `1:1`,如果要设置 `16:9` 请设置 `1.7777777777777777` 即 `16 / 9`。。 |
|
||||
| crop.rotatable | `boolean` | `false` | 裁剪时是否可旋转 |
|
||||
| crop.scalable | `boolean` | `false` | 裁剪时是否可缩放 |
|
||||
| crop.viewMode | `number` | `1` | 裁剪时的查看模式,0 是无限制 |
|
||||
| cropFormat | `string` | `image/png` | 裁剪文件格式 |
|
||||
| cropQuality | `number` | `1` | 裁剪文件格式的质量,用于 jpeg/webp,取值在 0 和 1 之间 |
|
||||
| limit | Limit | | 限制图片大小,超出不让上传。 |
|
||||
| frameImage | `string` | | 默认占位图地址 |
|
||||
| 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` | 图片选择器初始化后是否立即进入裁剪模式 |
|
||||
| draggable | `boolean` | false | 开启后支持拖拽排序改变图片值顺序 |
|
||||
| draggableTip | `string` | '拖拽排序' | 拖拽提示文案 |
|
||||
|
||||
### Limit 属性表
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
||||
var(--inputImage-base-default-bottom-left-border-radius);
|
||||
cursor: pointer;
|
||||
margin-right: var(--gap-base);
|
||||
|
||||
|
||||
&-bg {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
@ -119,11 +119,10 @@
|
||||
}
|
||||
|
||||
&:not(:disabled):not(.is-disabled) {
|
||||
|
||||
&.is-invalid:hover {
|
||||
border-color: var(--FileControl-danger-color);
|
||||
}
|
||||
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
color: var(--inputImage-base-hover-icon-color);
|
||||
@ -198,6 +197,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-itemList {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
&-item {
|
||||
border: var(--borderWidth) solid var(--borderColor);
|
||||
border-radius: var(--ImageControl-addBtn-borderRadius);
|
||||
@ -221,6 +224,10 @@
|
||||
svg.icon-refresh {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
&--dragging {
|
||||
border: var(--borderWidth) solid var(--colors-brand-5);
|
||||
}
|
||||
}
|
||||
|
||||
&-filename {
|
||||
|
@ -156,6 +156,7 @@ register('de-DE', {
|
||||
'Iframe.invalid': 'Ungültige Iframe-URL',
|
||||
'Iframe.invalidProtocol':
|
||||
'HTTP-URL-Iframe kann nicht in https verwendet werden',
|
||||
'Image.dragTip': 'Zum Sortieren ziehen',
|
||||
'Image.upload': 'Bild hochladen',
|
||||
'Image.configError':
|
||||
'Es können nur eine Beschneidung oder mehrere festgelegt werden',
|
||||
|
@ -151,6 +151,7 @@ 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.dragTip': 'Drag to sort',
|
||||
'Image.upload': 'Upload image',
|
||||
'Image.errorRetry': 'upload failed, please try again',
|
||||
'Image.configError': 'Can only set one of crop or multiple',
|
||||
|
@ -155,6 +155,7 @@ register('zh-CN', {
|
||||
'Form.nestedError': '表单不要直接嵌套在表单下面',
|
||||
'Iframe.invalid': 'iframe 地址不合法',
|
||||
'Iframe.invalidProtocol': '无法加载 http 协议的 iframe',
|
||||
'Image.dragTip': '拖拽排序',
|
||||
'Image.upload': '图片上传',
|
||||
'Image.errorRetry': '上传失败,请重试',
|
||||
'Image.configError': '图片多选配置和裁剪配置只能设置一个',
|
||||
|
@ -40,6 +40,7 @@ import isPlainObject from 'lodash/isPlainObject';
|
||||
import merge from 'lodash/merge';
|
||||
import omit from 'lodash/omit';
|
||||
import {TplSchema} from '../Tpl';
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
/**
|
||||
* Image 图片上传控件
|
||||
@ -284,6 +285,18 @@ export interface ImageControlSchema extends FormBaseControlSchema {
|
||||
* 固定尺寸的 CSS类名
|
||||
*/
|
||||
fixedSizeClassName?: SchemaClassName;
|
||||
|
||||
/**
|
||||
* 是否可拖拽排序
|
||||
*/
|
||||
draggable?: boolean;
|
||||
|
||||
/**
|
||||
* 可拖拽排序的提示信息。
|
||||
*
|
||||
* @default 可拖拽排序
|
||||
*/
|
||||
draggableTip?: string;
|
||||
}
|
||||
|
||||
let preventEvent = (e: any) => e.stopPropagation();
|
||||
@ -403,6 +416,7 @@ export default class ImageControl extends React.Component<
|
||||
};
|
||||
|
||||
files: Array<FileValue | FileX> = [];
|
||||
fileKeys: WeakMap<FileValue | FileX, string> = new WeakMap();
|
||||
fileUploadCancelExecutors: Array<{
|
||||
file: any;
|
||||
executor: () => void;
|
||||
@ -470,6 +484,7 @@ export default class ImageControl extends React.Component<
|
||||
this.syncAutoFill = this.syncAutoFill.bind(this);
|
||||
this.handleReSelect = this.handleReSelect.bind(this);
|
||||
this.handleFileCancel = this.handleFileCancel.bind(this);
|
||||
this.dragTipRef = this.dragTipRef.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -549,6 +564,17 @@ export default class ImageControl extends React.Component<
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unmounted = true;
|
||||
this.fileKeys = new WeakMap();
|
||||
}
|
||||
|
||||
getFileKey(file: FileValue | FileX) {
|
||||
if (this.fileKeys.has(file)) {
|
||||
return this.fileKeys.get(file);
|
||||
}
|
||||
|
||||
const key = guid();
|
||||
this.fileKeys.set(file, key);
|
||||
return key;
|
||||
}
|
||||
|
||||
buildCrop(props: ImageProps) {
|
||||
@ -1438,6 +1464,56 @@ export default class ImageControl extends React.Component<
|
||||
);
|
||||
}
|
||||
|
||||
dragTip?: HTMLElement;
|
||||
sortable?: Sortable;
|
||||
id: string = guid();
|
||||
dragTipRef(ref: any) {
|
||||
if (!this.dragTip && ref) {
|
||||
this.initDragging(ref.parentNode as HTMLElement);
|
||||
} else if (this.dragTip && !ref) {
|
||||
this.destroyDragging();
|
||||
}
|
||||
|
||||
this.dragTip = ref;
|
||||
}
|
||||
|
||||
initDragging(dom: HTMLElement) {
|
||||
const ns = this.props.classPrefix;
|
||||
this.sortable = new Sortable(dom, {
|
||||
group: `inputimages-${this.id}`,
|
||||
animation: 150,
|
||||
handle: `.${ns}ImageControl-item [data-role="dragBar"]`,
|
||||
ghostClass: `${ns}ImageControl-item--dragging`,
|
||||
onEnd: (e: any) => {
|
||||
// 没有移动
|
||||
if (e.newIndex === e.oldIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 换回来
|
||||
const parent = e.to as HTMLElement;
|
||||
if (e.oldIndex < parent.childNodes.length - 1) {
|
||||
parent.insertBefore(e.item, parent.childNodes[e.oldIndex]);
|
||||
} else {
|
||||
parent.appendChild(e.item);
|
||||
}
|
||||
|
||||
const files = this.files.concat();
|
||||
files.splice(e.newIndex, 0, files.splice(e.oldIndex, 1)[0]);
|
||||
this.setState(
|
||||
{
|
||||
files: (this.files = files)
|
||||
},
|
||||
() => this.onChange(true)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
destroyDragging() {
|
||||
this.sortable && this.sortable.destroy();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
@ -1462,7 +1538,9 @@ export default class ImageControl extends React.Component<
|
||||
addBtnControlClassName,
|
||||
iconControlClassName,
|
||||
id,
|
||||
translate: __
|
||||
translate: __,
|
||||
draggable,
|
||||
draggableTip
|
||||
} = this.props;
|
||||
|
||||
insertCustomStyle(
|
||||
@ -1529,6 +1607,10 @@ export default class ImageControl extends React.Component<
|
||||
}
|
||||
const filterFrameImage = filter(frameImage, this.props.data, '| raw');
|
||||
const hasPending = files.some(file => file.state == 'pending');
|
||||
|
||||
const enableDraggable =
|
||||
!!multiple && draggable && !disabled && !hasPending && files.length > 1;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(`ImageControl`, className, inputImageControlClassName)}
|
||||
@ -1616,10 +1698,11 @@ export default class ImageControl extends React.Component<
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{files && files.length
|
||||
? files.map((file, key) => (
|
||||
{files && files.length ? (
|
||||
<div className={cx('ImageControl-itemList')}>
|
||||
{files.map((file, key) => (
|
||||
<div
|
||||
key={file.id || key}
|
||||
key={this.getFileKey(file)}
|
||||
className={cx(
|
||||
'ImageControl-item',
|
||||
{
|
||||
@ -1755,6 +1838,22 @@ export default class ImageControl extends React.Component<
|
||||
thumbRatio={thumbRatio}
|
||||
overlays={
|
||||
<>
|
||||
{enableDraggable ? (
|
||||
<a
|
||||
data-role="dragBar"
|
||||
data-tooltip={__(
|
||||
draggableTip || 'Image.dragTip'
|
||||
)}
|
||||
data-position="bottom"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<Icon
|
||||
icon="drag-bar"
|
||||
className="icon"
|
||||
/>
|
||||
</a>
|
||||
) : null}
|
||||
<a
|
||||
data-tooltip={__('Image.zoomIn')}
|
||||
data-position="bottom"
|
||||
@ -1820,23 +1919,27 @@ export default class ImageControl extends React.Component<
|
||||
</a>
|
||||
) : null}
|
||||
{/* <a
|
||||
data-tooltip={
|
||||
file.name ||
|
||||
getNameFromUrl(file.value || file.url)
|
||||
}
|
||||
data-position="bottom"
|
||||
target="_blank"
|
||||
>
|
||||
<Icon icon="info" className="icon" />
|
||||
</a> */}
|
||||
data-tooltip={
|
||||
file.name ||
|
||||
getNameFromUrl(file.value || file.url)
|
||||
}
|
||||
data-position="bottom"
|
||||
target="_blank"
|
||||
>
|
||||
<Icon icon="info" className="icon" />
|
||||
</a> */}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
))}
|
||||
{enableDraggable ? (
|
||||
<span ref={this.dragTipRef} />
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{(multiple && (!maxLength || files.length < maxLength)) ||
|
||||
(!multiple && !files.length) ? (
|
||||
|
Loading…
Reference in New Issue
Block a user