feat: Image/Images组件工具栏支持自定义 (#5247)

This commit is contained in:
RUNZE LU 2022-09-29 18:49:13 +08:00 committed by GitHub
parent 507fca934b
commit 86e15457b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 296 additions and 206 deletions

View File

@ -322,10 +322,28 @@ List 的内容、Card 卡片的内容配置同上
}
```
## 工具栏
> 2.2.0 及以上版本
配置`"showToolbar": true`使图片在放大模式下开启图片工具栏。配置`"toolbarActions"`属性可以自定义工具栏的展示方式,具体配置参考[ImageAction](./image#imageaction)
```schema
{
"type": "page",
"body": {
"type": "image",
"src": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg@s_0,w_216,l_1,f_jpg,q_80",
"enlargeAble": true,
"showToolbar": true
}
}
```
## 属性表
| 属性名 | 类型 | 默认值 | 说明 |
| -------------- | ------------------------------------ | --------- | -------------------------------------------------------------------------------------- |
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| -------------- | ------------------------------------ | --------- | -------------------------------------------------------------------------------------- | ------- |
| type | `string` | | 如果在 Table、Card 和 List 中,为`"image"`;在 Form 中用作静态展示,为`"static-image"` |
| className | `string` | | 外层 CSS 类名 |
| innerClassName | `string` | | 组件内层 CSS 类名 |
@ -346,3 +364,22 @@ List 的内容、Card 卡片的内容配置同上
| thumbMode | `string` | `contain` | 预览图模式,可选:`'w-full'`, `'h-full'`, `'contain'`, `'cover'` |
| thumbRatio | `string` | `1:1` | 预览图比例,可选:`'1:1'`, `'4:3'`, `'16:9'` |
| imageMode | `string` | `thumb` | 图片展示模式,可选:`'thumb'`, `'original'` 即:缩略图模式 或者 原图模式 |
| showToolbar | `boolean` | `false` | 放大模式下是否展示图片的工具栏 | `2.2.0` |
| toolbarActions | `ImageAction[]` | | 图片工具栏,支持旋转,缩放,默认操作全部开启 | `2.2.0` |
#### ImageAction
```typescript
interface ImageAction {
/* 操作key */
key: 'rotateRight' | 'rotateLeft' | 'zoomIn' | 'zoomOut' | 'scaleOrigin';
/* 动作名称 */
label?: string;
/* 动作icon */
icon?: string;
/* 动作自定义CSS类 */
iconClassName?: string;
/* 动作是否禁用 */
disabled?: boolean;
}
```

View File

@ -66,12 +66,12 @@ order: 53
```ts
Array<{
image: string; // 小图,预览图
src?: string; // 原图
title?: string; // 标题
description?: string; // 描述
[propName:string]: any; // 还可以有其他数据
}>
image: string; // 小图,预览图
src?: string; // 原图
title?: string; // 标题
description?: string; // 描述
[propName: string]: any; // 还可以有其他数据
}>;
```
### 配置预览图地址
@ -454,18 +454,67 @@ List 的内容、Card 卡片的内容配置同上
}
```
## 工具栏
> 2.2.0 及以上版本
配置`"showToolbar": true`使图片在放大模式下开启图片工具栏。配置`"toolbarActions"`属性可以自定义工具栏的展示方式,具体配置参考[ImageAction](./image#imageaction)
```schema
{
"type": "page",
"data": {
"images": [
{
"image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg@s_0,w_216,l_1,f_jpg,q_80",
"a": "aaa1",
"b": "bbb1"
},
{
"image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692942/d8e4992057f9.jpeg@s_0,w_216,l_1,f_jpg,q_80",
"a": "aaa2",
"b": "bbb2"
},
{
"image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693148/1314a2a3d3f6.jpeg@s_0,w_216,l_1,f_jpg,q_80",
"a": "aaa3",
"b": "bbb3"
},
{
"image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693379/8f2e79f82be0.jpeg@s_0,w_216,l_1,f_jpg,q_80",
"a": "aaa4",
"b": "bbb4"
},
{
"image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693566/552b175ef11d.jpeg@s_0,w_216,l_1,f_jpg,q_80",
"a": "aaa5",
"b": "bbb5"
}
]
},
"body": {
"type": "images",
"source": "${images}",
"enlargeAble": true,
"showToolbar": true
}
}
```
## 属性表
| 属性名 | 类型 | 默认值 | 说明 |
| ------------ | ------------------------------------------ | --------- | ---------------------------------------------------------------------------------------- |
| type | `string` | `images` | 如果在 Table、Card 和 List 中,为`"images"`;在 Form 中用作静态展示,为`"static-images"` |
| className | `string` | | 外层 CSS 类名 |
| defaultImage | `string` | | 默认展示图片 |
| value | `string`或`Array<string>`或`Array<object>` | | 图片数组 |
| source | `string` | | 数据源 |
| delimiter | `string` | `,` | 分隔符,当 value 为字符串时,用该值进行分隔拆分 |
| src | `string` | | 预览图地址,支持数据映射获取对象中图片变量 |
| originalSrc | `string` | | 原图地址,支持数据映射获取对象中图片变量 |
| enlargeAble | `boolean` | | 支持放大预览 |
| thumbMode | `string` | `contain` | 预览图模式,可选:`'w-full'`, `'h-full'`, `'contain'`, `'cover'` |
| thumbRatio | `string` | `1:1` | 预览图比例,可选:`'1:1'`, `'4:3'`, `'16:9'` |
| 属性名 | 类型 | 默认值 | 说明 | 版本 |
| -------------- | ------------------------------------------ | --------- | ---------------------------------------------------------------------------------------- | ------- |
| type | `string` | `images` | 如果在 Table、Card 和 List 中,为`"images"`;在 Form 中用作静态展示,为`"static-images"` |
| className | `string` | | 外层 CSS 类名 |
| defaultImage | `string` | | 默认展示图片 |
| value | `string`或`Array<string>`或`Array<object>` | | 图片数组 |
| source | `string` | | 数据源 |
| delimiter | `string` | `,` | 分隔符,当 value 为字符串时,用该值进行分隔拆分 |
| src | `string` | | 预览图地址,支持数据映射获取对象中图片变量 |
| originalSrc | `string` | | 原图地址,支持数据映射获取对象中图片变量 |
| enlargeAble | `boolean` | | 支持放大预览 |
| thumbMode | `string` | `contain` | 预览图模式,可选:`'w-full'`, `'h-full'`, `'contain'`, `'cover'` |
| thumbRatio | `string` | `1:1` | 预览图比例,可选:`'1:1'`, `'4:3'`, `'16:9'` |
| showToolbar | `boolean` | `false` | 放大模式下是否展示图片的工具栏 | `2.2.0` |
| toolbarActions | `ImageAction[]` | | 图片工具栏,支持旋转,缩放,默认操作全部开启 | `2.2.0` |

View File

@ -31,6 +31,7 @@ export interface ImageAction {
export interface ImageGalleryProps extends ThemeProps, LocaleProps {
children: React.ReactNode;
modalContainer?: () => HTMLElement;
/** 操作栏 */
actions?: ImageAction[];
}
@ -47,6 +48,10 @@ export interface ImageGalleryState {
scale: number;
/** 图片旋转角度 */
rotate: number;
/** 是否开启操作栏 */
showToolbar?: boolean;
/** 工具栏配置 */
actions?: ImageAction[];
}
export class ImageGallery extends React.Component<
@ -80,7 +85,9 @@ export class ImageGallery extends React.Component<
index: -1,
items: [],
scale: 1,
rotate: 0
rotate: 0,
showToolbar: false,
actions: ImageGallery.defaultProps.actions
};
@autobind
@ -96,11 +103,24 @@ export class ImageGallery extends React.Component<
title?: string;
caption?: string;
index?: number;
showToolbar?: boolean;
toolbarActions?: ImageAction[];
}) {
const {actions} = this.props;
const validActionKeys = Object.values(ImageActionKey);
this.setState({
isOpened: true,
items: info.list ? info.list : [info],
index: info.index || 0
index: info.index || 0,
/* children组件可以控制工具栏的展示 */
showToolbar: !!info.showToolbar,
/** 外部传入合法key值的actions才会生效 */
actions: Array.isArray(info.toolbarActions)
? info.toolbarActions.filter(action =>
validActionKeys.includes(action?.key)
)
: actions
});
}
@ -212,8 +232,8 @@ export class ImageGallery extends React.Component<
}
render() {
const {children, classnames: cx, modalContainer, actions} = this.props;
const {index, items, rotate, scale} = this.state;
const {children, classnames: cx, modalContainer} = this.props;
const {index, items, rotate, scale, showToolbar, actions} = this.state;
const __ = this.props.translate;
return (
@ -249,7 +269,7 @@ export class ImageGallery extends React.Component<
style={{transform: `scale(${scale}) rotate(${rotate}deg)`}}
/>
{Array.isArray(actions) && actions.length > 0
{showToolbar && Array.isArray(actions) && actions.length > 0
? this.renderToolbar(actions)
: null}

View File

@ -1,8 +1,33 @@
import React = require('react');
import {cleanup, fireEvent, render, waitFor} from '@testing-library/react';
/**
* CRUD
*
* 01. interval & headerToolbar & footerToolbar
* 02. stopAutoRefreshWhen
* 03. loadDataOnce
* 04. list模式
* 05. card模式
* 06. source & alwaysShowPagination
* 07. filter
* 08. draggable & itemDraggableOn
* 09. quickEdit & quickSaveApi
* 10. quickSaveItemApi
* 11. bulkActions
* 12. sortable & orderBy & orderDir & orderField
* 13. keepItemSelectionOnPageChange & maxKeepItemSelectionLength & labelTpl
* 14. autoGenerateFilter
* 15. group
*/
import {
cleanup,
fireEvent,
render,
waitFor,
waitForElementToBeRemoved
} from '@testing-library/react';
import '../../src';
import {clearStoresCache, render as amisRender} from '../../src';
import {makeEnv, wait} from '../helper';
import {makeEnv as makeEnvRaw, wait} from '../helper';
import rows from '../mockData/rows';
afterEach(() => {
@ -11,6 +36,9 @@ afterEach(() => {
jest.useRealTimers();
});
/** 避免updateLocation里的console.error */
const makeEnv = args => makeEnvRaw({updateLocation: () => {}, ...args});
async function fetcher(config: any) {
return {
status: 200,
@ -171,16 +199,8 @@ test('Renderer:crud list', async () => {
makeEnv({fetcher})
)
);
await waitFor(() => {
expect(
container.querySelector('[data-testid="spinner"]')
).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
await waitFor(() => {
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
expect(container.querySelectorAll('.cxd-ListItem').length > 5).toBeTruthy();
});
expect(container).toMatchSnapshot();
@ -223,15 +243,6 @@ test('Renderer:crud cards', async () => {
);
await waitFor(() => {
expect(
container.querySelector('[data-testid="spinner"]')
).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
await waitFor(() => {
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
expect(container.querySelector('.cxd-Card-title')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();

View File

@ -1,60 +1,70 @@
import React = require('react');
/**
* Image/Images /
*
* 1. Image图片
* 2. Images图片集
*/
import {render} from '@testing-library/react';
import '../../src';
import {render as amisRender} from '../../src';
import {makeEnv} from '../helper';
test('Renderer:image', async () => {
const {container} = render(
amisRender(
{
type: 'image',
defaultImage: 'https://www.baidu.com/img/bd_logo1.png',
title: '图片',
description: '图片描述',
imageClassName: 'b',
className: 'show'
},
{},
makeEnv({})
)
);
describe('Renderer:image', () => {
test('image:basic', async () => {
const {container} = render(
amisRender(
{
type: 'image',
defaultImage: 'https://www.baidu.com/img/bd_logo1.png',
title: '图片',
description: '图片描述',
imageClassName: 'b',
className: 'show'
},
{},
makeEnv({})
)
);
expect(container).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
});
test('Renderer:images', async () => {
const {container} = render(
amisRender(
{
type: 'page',
data: {
imageList: [
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg@s_0,w_216,l_1,f_jpg,q_80',
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692942/d8e4992057f9.jpeg@s_0,w_216,l_1,f_jpg,q_80',
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693148/1314a2a3d3f6.jpeg@s_0,w_216,l_1,f_jpg,q_80',
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693379/8f2e79f82be0.jpeg@s_0,w_216,l_1,f_jpg,q_80',
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693566/552b175ef11d.jpeg@s_0,w_216,l_1,f_jpg,q_80'
describe('Renderer:images', () => {
test('images:basic', async () => {
const {container} = render(
amisRender(
{
type: 'page',
data: {
imageList: [
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg@s_0,w_216,l_1,f_jpg,q_80',
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692942/d8e4992057f9.jpeg@s_0,w_216,l_1,f_jpg,q_80',
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693148/1314a2a3d3f6.jpeg@s_0,w_216,l_1,f_jpg,q_80',
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693379/8f2e79f82be0.jpeg@s_0,w_216,l_1,f_jpg,q_80',
'https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693566/552b175ef11d.jpeg@s_0,w_216,l_1,f_jpg,q_80'
]
},
body: [
{
type: 'images',
source: '${imageList}'
},
{
type: 'divider'
},
{
type: 'images',
name: 'imageList'
}
]
},
body: [
{
type: 'images',
source: '${imageList}'
},
{
type: 'divider'
},
{
type: 'images',
name: 'imageList'
}
]
},
{},
makeEnv({})
)
);
{},
makeEnv({})
)
);
expect(container).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
});

View File

@ -2933,60 +2933,6 @@ exports[`Renderer:crud basic interval headerToolbar footerToolbar 1`] = `
`;
exports[`Renderer:crud cards 1`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
>
<div
class="cxd-Crud is-loading"
>
<div
class="cxd-Cards cxd-Crud-body"
>
<div
class="cxd-Cards-fixedTop"
/>
<div
class="cxd-Cards-placeholder"
>
<span
class="cxd-TplField"
>
<span>
没有用户信息
</span>
</span>
</div>
<div
class="cxd-Spinner-overlay in"
/>
<div
class="cxd-Spinner cxd-Spinner--overlay in"
data-testid="spinner"
>
<div
class="cxd-Spinner-icon cxd-Spinner-icon--default"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Renderer:crud cards 2`] = `
<div>
<div
class="cxd-Page"
@ -3846,56 +3792,11 @@ exports[`Renderer:crud cards 2`] = `
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Renderer:crud list 1`] = `
<div>
<div
class="cxd-Page"
>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-body"
>
<div
class="cxd-Crud is-loading"
>
<div
class="cxd-List cxd-Crud-body"
>
<div
class="cxd-List-heading"
>
list title
</div>
<div
class="cxd-List-placeholder"
>
<span
class="cxd-TplField"
>
<span>
当前组内, 还没有配置任何权限.
</span>
</span>
</div>
<div
class="cxd-Spinner-overlay in"
class="cxd-Spinner-overlay"
/>
<div
class="cxd-Spinner cxd-Spinner--overlay in"
class="cxd-Spinner cxd-Spinner--overlay"
data-testid="spinner"
>
<div
@ -3911,7 +3812,7 @@ exports[`Renderer:crud list 1`] = `
</div>
`;
exports[`Renderer:crud list 2`] = `
exports[`Renderer:crud list 1`] = `
<div>
<div
class="cxd-Page"
@ -4293,6 +4194,17 @@ exports[`Renderer:crud list 2`] = `
</div>
</div>
</div>
<div
class="cxd-Spinner-overlay"
/>
<div
class="cxd-Spinner cxd-Spinner--overlay"
data-testid="spinner"
>
<div
class="cxd-Spinner-icon cxd-Spinner-icon--default"
/>
</div>
</div>
</div>
</div>

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Renderer:image 1`] = `
exports[`Renderer:image image:basic 1`] = `
<div>
<div
class="cxd-ImageField cxd-ImageField--thumb show"
@ -35,7 +35,7 @@ exports[`Renderer:image 1`] = `
</div>
`;
exports[`Renderer:images 1`] = `
exports[`Renderer:images images:basic 1`] = `
<div>
<div
class="cxd-Page"

View File

@ -1,13 +1,24 @@
import React from 'react';
import {Renderer, RendererProps} from 'amis-core';
import {filter} from 'amis-core';
import {ClassNamesFn, themeable, ThemeProps} from 'amis-core';
import {themeable, ThemeProps} from 'amis-core';
import {autobind, getPropValue} from 'amis-core';
import {Icon} from 'amis-ui';
import {LocaleProps, localeable} from 'amis-core';
import {BaseSchema, SchemaClassName, SchemaTpl, SchemaUrlPath} from '../Schema';
import {resolveVariable} from 'amis-core';
import {handleAction} from 'amis-core';
import type {
ImageAction,
ImageActionKey
} from 'amis-ui/lib/components/ImageGallery';
export interface ImageToolbarAction {
key: keyof typeof ImageActionKey;
label?: string;
icon?: string;
iconClassName?: string;
disabled?: boolean;
}
/**
*
@ -124,6 +135,16 @@ export interface ImageSchema extends BaseSchema {
* target
*/
htmlTarget?: string;
/**
*
*/
showToolbar?: boolean;
/**
*
*/
toolbarActions?: ImageToolbarAction[];
}
export interface ImageThumbProps
@ -284,6 +305,8 @@ export interface ImageFieldProps extends RendererProps {
thumbRatio: '1:1' | '4:3' | '16:9';
originalSrc?: string; // 原图
enlargeAble?: boolean;
showToolbar?: boolean;
toolbarActions?: ImageAction[];
onImageEnlarge?: (
info: {
src: string;
@ -292,6 +315,8 @@ export interface ImageFieldProps extends RendererProps {
caption?: string;
thumbMode?: 'w-full' | 'h-full' | 'contain' | 'cover';
thumbRatio?: '1:1' | '4:3' | '16:9';
showToolbar?: boolean;
toolbarActions?: ImageAction[];
},
target: any
) => void;
@ -317,7 +342,13 @@ export class ImageField extends React.Component<ImageFieldProps, object> {
thumbMode,
thumbRatio
}: ImageThumbProps) {
const {onImageEnlarge, enlargeTitle, enlargeCaption} = this.props;
const {
onImageEnlarge,
enlargeTitle,
enlargeCaption,
showToolbar,
toolbarActions
} = this.props;
onImageEnlarge &&
onImageEnlarge(
@ -327,7 +358,9 @@ export class ImageField extends React.Component<ImageFieldProps, object> {
title: enlargeTitle || title,
caption: enlargeCaption || caption,
thumbMode,
thumbRatio
thumbRatio,
showToolbar,
toolbarActions
},
this.props
);

View File

@ -9,6 +9,7 @@ import {
import Image, {ImageThumbProps, imagePlaceholder} from './Image';
import {autobind, getPropValue} from 'amis-core';
import {BaseSchema, SchemaClassName, SchemaUrlPath} from '../Schema';
import type {ImageToolbarAction} from './Image';
/**
*
@ -86,17 +87,29 @@ export interface ImagesSchema extends BaseSchema {
* CSS
*/
listClassName?: SchemaClassName;
/**
*
*/
showToolbar?: boolean;
/**
*
*/
toolbarActions?: ImageToolbarAction[];
}
export interface ImagesProps
extends RendererProps,
Omit<ImagesSchema, 'type' | 'className'> {
delimiter: string;
onEnlarge?: (
info: ImageThumbProps & {
list?: Array<
Pick<ImageThumbProps, 'src' | 'originalSrc' | 'title' | 'caption'>
Pick<
ImageThumbProps,
'src' | 'originalSrc' | 'title' | 'caption' | 'showToolbar'
>
>;
}
) => void;
@ -163,7 +176,9 @@ export class ImagesField extends React.Component<ImagesProps> {
src,
originalSrc,
listClassName,
options
options,
showToolbar,
toolbarActions
} = this.props;
let value: any;
@ -212,6 +227,8 @@ export class ImagesField extends React.Component<ImagesProps> {
thumbRatio={thumbRatio}
enlargeAble={enlargeAble!}
onEnlarge={this.handleEnlarge}
showToolbar={showToolbar}
toolbarActions={toolbarActions}
/>
))}
</div>

View File

@ -305,6 +305,7 @@ main().catch(e => {
const sourceFile = node.getSourceFile();
const position = sourceFile.getLineAndCharacterOfPosition(node.pos);
console.log(sourceFile, position);
console.log(
`\x1b[36m${sourceFile.fileName}:${position.line + 1}:${
position.character + 1