From b69f9c5b08693c4eb0f83b8d6bbbf4781089b6c4 Mon Sep 17 00:00:00 2001 From: xiangwaner <1186355501@qq.com> Date: Tue, 14 Mar 2023 15:10:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:carousel=E6=94=AF=E6=8C=81=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E5=8A=A8=E7=94=BB=E6=A8=A1=E5=BC=8F=20(#6354)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:carousel支持多图配置 * Update carousel.md --------- Co-authored-by: zhaowenli Co-authored-by: RUNZE LU <36724300+lurunze1226@users.noreply.github.com> --- docs/zh-CN/components/carousel.md | 40 ++++- .../amis-ui/scss/components/_carousel.scss | 9 + packages/amis/src/renderers/Carousel.tsx | 162 +++++++++++++++--- 3 files changed, 183 insertions(+), 28 deletions(-) diff --git a/docs/zh-CN/components/carousel.md b/docs/zh-CN/components/carousel.md index 7feb8c129..2a0e8499e 100755 --- a/docs/zh-CN/components/carousel.md +++ b/docs/zh-CN/components/carousel.md @@ -101,10 +101,41 @@ itemSchema: { } ``` +## 多图模式 + +> `2.8.1` 及以上版本 + +```schema: scope="body" +{ + "type": "carousel", + "auto": true, + "thumbMode": "cover", + "animation": "slide", + "multiple": {count: 3}, + "interval": 0, + "duration": 5000, + "height": 300, + "options": [ + { + "image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg@s_0,w_216,l_1,f_jpg,q_80" + }, + { + "image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692942/d8e4992057f9.jpeg@s_0,w_216,l_1,f_jpg,q_80" + }, + { + "image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693148/1314a2a3d3f6.jpeg@s_0,w_216,l_1,f_jpg,q_80" + }, + { + "image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693379/8f2e79f82be0.jpeg@s_0,w_216,l_1,f_jpg,q_80" + } + ] +} +``` + ## 属性表 -| 属性名 | 类型 | 默认值 | 说明 | -| ---------------------------- | --------- | ---------------------- | ------------------------------------------------------- | +| 属性名 | 类型 | 默认值 | 说明 | 版本 | +| ---------------------------- | --------- | ---------------------- | ------------------------------------------------------- | --- | | type | `string` | `"carousel"` | 指定为 Carousel 渲染器 | | className | `string` | `"panel-default"` | 外层 Dom 的类名 | | options | `array` | `[]` | 轮播面板数据 | @@ -119,13 +150,16 @@ itemSchema: { | itemSchema | `object` | | 自定义`schema`来展示数据 | | auto | `boolean` | `true` | 是否自动轮播 | | interval | `string` | `5s` | 切换动画间隔 | -| duration | `string` | `0.5s` | 切换动画时长 | +| duration | `number` | `500` | 切换动画时长(ms) | | width | `string` | `auto` | 宽度 | | height | `string` | `200px` | 高度 | | controls | `array` | `['dots', 'arrows']` | 显示左右箭头、底部圆点索引 | | controlsTheme | `string` | `light` | 左右箭头、底部圆点索引颜色,默认`light`,另有`dark`模式 | | animation | `string` | fade | 切换动画效果,默认`fade`,另有`slide`模式 | | thumbMode | `string` | `"cover" \| "contain"` | 图片默认缩放模式 | +| multiple | `object` | `{count: 1}` | 多图模式,count表示展示的数量 | `2.8.1` | +| alwaysShowArrow | `boolean` | `false` | 是否一直显示箭头,为false时鼠标hover才会显示 | `2.8.1` | +| icons | {prev: `SchemaCollection`; next: `SchemaCollection`;} | | 自定义箭头图标 | `2.8.1` | ## 事件表 diff --git a/packages/amis-ui/scss/components/_carousel.scss b/packages/amis-ui/scss/components/_carousel.scss index 0484b4041..03d40ff74 100644 --- a/packages/amis-ui/scss/components/_carousel.scss +++ b/packages/amis-ui/scss/components/_carousel.scss @@ -176,6 +176,15 @@ right: 0; } + &.#{$ns}Carousel-arrow--always { + .#{$ns}Carousel-leftArrow { + display: block; + } + .#{$ns}Carousel-rightArrow { + display: block; + } + } + &:hover { .#{$ns}Carousel-leftArrow { display: block; diff --git a/packages/amis/src/renderers/Carousel.tsx b/packages/amis/src/renderers/Carousel.tsx index 9d267a458..f6f87ecef 100644 --- a/packages/amis/src/renderers/Carousel.tsx +++ b/packages/amis/src/renderers/Carousel.tsx @@ -2,7 +2,8 @@ import React from 'react'; import Transition, { ENTERED, ENTERING, - EXITING + EXITING, + EXITED } from 'react-transition-group/Transition'; import {Renderer, RendererProps} from 'amis-core'; import {resolveVariableAndFilter} from 'amis-core'; @@ -88,6 +89,26 @@ export interface CarouselSchema extends BaseSchema { * 配置固定值 */ options?: Array; + + /** + * 是否一直显示箭头 + */ + alwaysShowArrow?: boolean; + + /** + * 多图模式配置项 + */ + multiple?: { + count: number + }; + + /** + * 自定义箭头图标 + */ + icons?: { + prev?: SchemaCollection; + next?: SchemaCollection; + }; } const animationStyles: { @@ -108,6 +129,7 @@ export interface CarouselState { current: number; options: any[]; nextAnimation: string; + loading: boolean; } const defaultSchema = { @@ -156,6 +178,8 @@ export class Carousel extends React.Component { | 'animation' | 'controls' | 'placeholder' + | 'multiple' + | 'alwaysShowArrow' > = { auto: true, interval: 5000, @@ -163,13 +187,16 @@ export class Carousel extends React.Component { controlsTheme: 'light', animation: 'fade', controls: ['dots', 'arrows'], - placeholder: '-' + placeholder: '-', + multiple: {count: 1}, + alwaysShowArrow: false }; state = { current: 0, options: this.props.options || getPropValue(this.props) || [], - nextAnimation: '' + nextAnimation: '', + loading: false }; componentDidMount() { @@ -294,11 +321,19 @@ export class Carousel extends React.Component { @autobind next() { + const multiple = this.props.multiple; + if (this.state.loading && multiple && multiple.count > 1) { + return; + } this.autoSlide('next'); } @autobind prev() { + const multiple = this.props.multiple; + if (this.state.loading && multiple && multiple.count > 1) { + return; + } this.autoSlide('prev'); } @@ -310,8 +345,12 @@ export class Carousel extends React.Component { @autobind async changeSlide(index: number) { - const {current} = this.state; - const {dispatchEvent, data} = this.props; + const {current, loading} = this.state; + const {dispatchEvent, data, multiple} = this.props; + + if (loading && multiple && multiple.count > 1) { + return; + } const rendererEvent = await dispatchEvent( 'change', @@ -369,14 +408,37 @@ export class Carousel extends React.Component { @autobind handleMouseEnter() { + const multiple = this.props.multiple; + if (multiple && multiple.count > 1) { + return; + } this.clearAutoTimeout(); } @autobind handleMouseLeave() { + const multiple = this.props.multiple; + if (multiple && multiple.count > 1) { + return; + } this.prepareAutoSlide(); } + // 处理options + getNewOptions(options: any, count: number = 1) { + let newOptions: Array = options; + if (Array.isArray(options) && options.length) { + newOptions = new Array(options.length); + for (let i = 0; i < options.length; i++) { + newOptions[i] = new Array(count); + for(let j = 0; j < count; j++) { + newOptions[i][j] = options[(i + j) % options.length]; + } + } + } + return newOptions; + } + render() { const { render, @@ -391,9 +453,13 @@ export class Carousel extends React.Component { controlsTheme, placeholder, data, - name + name, + duration, + multiple, + alwaysShowArrow, + icons } = this.props; - const {options, current, nextAnimation} = this.state; + const {options, current, nextAnimation, loading} = this.state; let body: JSX.Element | null = null; let carouselStyles: { @@ -406,8 +472,17 @@ export class Carousel extends React.Component { controls!.indexOf('arrows') > -1 ]; const animationName = nextAnimation || animation; - + if (Array.isArray(options) && options.length) { + let multipleCount = 1; + if (multiple && typeof multiple.count === 'number' && multiple.count >= 2) { + multipleCount = Math.floor(multiple.count) < options.length ? Math.floor(multiple.count) : options.length; + } + const newOptions = this.getNewOptions(options, multipleCount); + const transitionDuration = multipleCount > 1 && typeof duration === 'number' + ? `${duration}ms`: (duration || '500ms'); + const timeout = multipleCount > 1 && typeof duration === 'number' ? duration : 500; + body = (
{ mountOnEnter unmountOnExit in={key === current} - timeout={500} + timeout={timeout} key={key} > {(status: string) => { @@ -430,6 +505,40 @@ export class Carousel extends React.Component { (item: HTMLElement) => item.offsetHeight ); } + if (multipleCount > 1) { + if ((status === ENTERING || status === EXITING) && !loading) { + this.setState({loading: true}); + } else if ((status === ENTERED || status === EXITED) && loading) { + this.setState({loading: false}); + } + } + + const transformStyles: { + [propName: string]: number; + } = { + [ENTERING]: 0, + [ENTERED]: 0, + [EXITING]: animationName === 'slideRight' ? 100 / multipleCount : -100 / multipleCount, + [EXITED]: animationName === 'slideRight' ? -100 / multipleCount : 100 / multipleCount + }; + const itemStyle = multipleCount > 1 ? { + transitionTimingFunction: 'linear', + transitionDuration: transitionDuration, + ...(animation === 'slide' ? {transform: `translateX(${transformStyles[status]}%)`} : {}) + } : {}; + const itemRender = (option: any) => render( + `${current}/body`, + itemSchema ? itemSchema : (defaultSchema as any), + { + thumbMode: this.props.thumbMode, + data: createObject( + data, + isObject(option) + ? option + : {item: option, [name!]: option} + ) + } + ); return (
{ animationName, animationStyles[status] )} + style={itemStyle} > - {render( - `${current}/body`, - itemSchema ? itemSchema : (defaultSchema as any), - { - thumbMode: this.props.thumbMode, - data: createObject( - data, - isObject(option) - ? option - : {item: option, [name!]: option} - ) - } - )} + {multipleCount === 1 ? itemRender(option) : null} + {multipleCount > 1 ? + newOptions[key].map((option: any, index: number) => ( +
+ {itemRender(option)} +
+ )) : null}
); }} @@ -463,7 +571,7 @@ export class Carousel extends React.Component { return (
{body ? body : placeholder} @@ -471,12 +579,16 @@ export class Carousel extends React.Component { {dots ? this.renderDots() : null} {arrows ? (
- + {icons && icons.prev + ? React.isValidElement(icons.prev) ? icons.prev : render('arrow-prev', icons.prev) + : ()}
) : null} {arrows ? (
- + {icons && icons.next + ? React.isValidElement(icons.next) ? icons.next : render('arrow-next', icons.next) + : ()}
) : null}