From 62cd1066daa1e76e4d8a4bc347c6b91863a6f350 Mon Sep 17 00:00:00 2001 From: tiantiancheng Date: Tue, 21 Jan 2025 15:59:13 +0800 Subject: [PATCH 01/14] =?UTF-8?q?feat:=20=E5=9B=BE=E7=89=87=E9=9B=86?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E6=94=AF=E6=8C=81=E7=A7=BB=E5=8A=A8=E7=AB=AF?= =?UTF-8?q?=E6=BB=91=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/amis-editor/src/plugin/Images.tsx | 33 ++- packages/amis-ui/scss/components/_images.scss | 72 +++++ packages/amis/src/renderers/Images.tsx | 247 ++++++++++++++---- 3 files changed, 303 insertions(+), 49 deletions(-) diff --git a/packages/amis-editor/src/plugin/Images.tsx b/packages/amis-editor/src/plugin/Images.tsx index fc3da2d7f4e..69c696c39f5 100644 --- a/packages/amis-editor/src/plugin/Images.tsx +++ b/packages/amis-editor/src/plugin/Images.tsx @@ -20,7 +20,8 @@ export class ImagesPlugin extends BasePlugin { pluginIcon = 'images-plugin'; scaffold = { type: 'images', - imageGallaryClassName: 'app-popover :AMISCSSWrapper' + imageGallaryClassName: 'app-popover :AMISCSSWrapper', + displayMode: 'thumb' // 默认缩略图模式 }; previewSchema = { ...this.scaffold, @@ -126,6 +127,22 @@ export class ImagesPlugin extends BasePlugin { } ] }, + { + type: 'select', + name: 'displayMode', + label: '图片集模式', + value: 'thumb', + options: [ + { + label: '缩略图模式', + value: 'thumb' + }, + { + label: '大图模式', + value: 'full' + } + ] + }, getSchemaTpl('switch', { name: 'enlargeAble', label: '图片放大功能' @@ -152,6 +169,14 @@ export class ImagesPlugin extends BasePlugin { // name: 'showDimensions', // label: '显示图片尺寸' // }), + { + name: 'galleryHeight', + type: 'input-text', + label: '画廊高度', + description: '支持单位: px, rem, vh等', + placeholder: '例如: 400px', + visibleOn: 'this.displayMode === "full"' + }, { name: 'thumbMode', @@ -184,7 +209,8 @@ export class ImagesPlugin extends BasePlugin { label: '铺满', value: 'cover' } - ] + ], + visibleOn: 'this.displayMode === "thumb"' }, { @@ -208,7 +234,8 @@ export class ImagesPlugin extends BasePlugin { label: '16:9', value: '16:9' } - ] + ], + visibleOn: 'this.displayMode === "thumb"' } ] }, diff --git a/packages/amis-ui/scss/components/_images.scss b/packages/amis-ui/scss/components/_images.scss index 108a149bccf..81c83c46a37 100644 --- a/packages/amis-ui/scss/components/_images.scss +++ b/packages/amis-ui/scss/components/_images.scss @@ -11,6 +11,78 @@ var(--image-images-item-marginRight) var(--image-images-item-marginBottom) var(--image-images-item-marginLeft); } + + &-index { + position: absolute; + right: 10px; + bottom: 10px; + background: rgba(0, 0, 0, 0.5); + color: white; + padding: 4px 8px; + border-radius: 12px; + font-size: 12px; + z-index: 1; + } +} + +.#{$ns}ImagesField { + position: relative; + + &--full { + position: relative; + + .#{$ns}Images { + position: relative; + overflow: hidden; + width: 100%; + height: var(--gallery-height, 400px); + cursor: grab; + + &-container { + display: flex; + width: 100%; + height: 100%; + will-change: transform; + user-select: none; + } + + &-item { + flex: 0 0 100%; + width: 100%; + height: 100%; + margin: 0; + display: flex; + align-items: center; + justify-content: center; + background: #fff; + + .#{$ns}Image-image { + max-width: 100%; + max-height: 100%; + width: auto; + height: auto; + object-fit: contain; + } + } + + &.is-swiping { + cursor: grabbing; + } + } + + .Images-index { + position: absolute; + right: 16px; + bottom: 16px; + background: rgba(0, 0, 0, 0.5); + color: white; + padding: 4px 8px; + border-radius: 12px; + font-size: 12px; + z-index: 10; + pointer-events: none; + } + } } .#{$ns}Image { diff --git a/packages/amis/src/renderers/Images.tsx b/packages/amis/src/renderers/Images.tsx index ca97e6440da..edb11bf01d7 100644 --- a/packages/amis/src/renderers/Images.tsx +++ b/packages/amis/src/renderers/Images.tsx @@ -112,6 +112,21 @@ export interface ImagesSchema extends BaseSchema { * 工具栏配置 */ toolbarActions?: ImageToolbarAction[]; + + /** + * 展示模式,支持缩略图模式(thumb)和大图模式(full) + */ + displayMode?: 'thumb' | 'full'; + + /** + * 当前展示图片索引 + */ + currentIndex?: number; + + /** + * 画廊高度,仅在大图模式下生效 + */ + galleryHeight?: number | string; } export interface ImagesProps @@ -130,25 +145,111 @@ export interface ImagesProps ) => void; } -export class ImagesField extends React.Component { - static defaultProps: Pick< - ImagesProps, - | 'className' - | 'delimiter' - | 'defaultImage' - | 'placehoder' - | 'thumbMode' - | 'thumbRatio' - > = { +export interface ImagesState { + currentIndex: number; + isSwiping: boolean; + startX: number; +} + +export class ImagesField extends React.Component { + static defaultProps = { className: '', delimiter: ',', defaultImage: imagePlaceholder, placehoder: '-', thumbMode: 'contain', - thumbRatio: '1:1' + thumbRatio: '1:1', + displayMode: 'thumb' + }; + + state: ImagesState = { + currentIndex: 0, + isSwiping: false, + startX: 0 }; list: Array = []; + containerRef = React.createRef(); + + @autobind + handleTouchStart(e: React.TouchEvent) { + if (this.props.displayMode !== 'full') return; + + this.setState({ + isSwiping: true, + startX: e.touches[0].clientX + }); + } + + @autobind + handleTouchEnd(e: React.TouchEvent) { + if (!this.state.isSwiping) return; + + const {currentIndex} = this.state; + const deltaX = e.changedTouches[0].clientX - this.state.startX; + const threshold = 50; // 滑动阈值 + + if (Math.abs(deltaX) > threshold) { + if (deltaX > 0 && currentIndex > 0) { + // 向右滑,显示上一张 + this.setState({currentIndex: currentIndex - 1}); + } else if (deltaX < 0 && currentIndex < this.list.length - 1) { + // 向左滑,显示下一张 + this.setState({currentIndex: currentIndex + 1}); + } + } + + this.setState({isSwiping: false}); + } + + componentDidMount() {} + componentWillUnmount() {} + + @autobind + handleMouseDown(e: React.MouseEvent) { + if (this.props.displayMode !== 'full') return; + + // 阻止图片默认的拖拽行为 + e.preventDefault(); + + this.setState({ + isSwiping: true, + startX: e.clientX + }); + + document.addEventListener('mousemove', this.handleMouseMove); + document.addEventListener('mouseup', this.handleMouseUp); + } + + @autobind + handleMouseMove(e: MouseEvent) { + if (!this.state.isSwiping) return; + e.preventDefault(); + } + + @autobind + handleMouseUp(e: MouseEvent) { + if (!this.state.isSwiping) return; + + const {currentIndex} = this.state; + const deltaX = e.clientX - this.state.startX; + const threshold = 50; // 滑动阈值 + + if (Math.abs(deltaX) > threshold) { + if (deltaX > 0 && currentIndex > 0) { + // 向右滑,显示上一张 + this.setState({currentIndex: currentIndex - 1}); + } else if (deltaX < 0 && currentIndex < this.list.length - 1) { + // 向左滑,显示下一张 + this.setState({currentIndex: currentIndex + 1}); + } + } + + this.setState({isSwiping: false}); + + document.removeEventListener('mousemove', this.handleMouseMove); + document.removeEventListener('mouseup', this.handleMouseUp); + } @autobind handleEnlarge(info: ImageThumbProps) { @@ -175,6 +276,23 @@ export class ImagesField extends React.Component { ); } + renderImageIndex() { + const {currentIndex} = this.state; + return ( +
+ {currentIndex + 1}/{this.list.length} +
+ ); + } + + getTransformStyle() { + const {currentIndex} = this.state; + return { + transform: `translateX(-${currentIndex * 100}%)`, + transition: this.state.isSwiping ? 'none' : 'transform 0.3s ease-out' + }; + } + render() { const { className, @@ -202,9 +320,13 @@ export class ImagesField extends React.Component { wrapperCustomStyle, env, themeCss, - imagesControlClassName + imagesControlClassName, + displayMode, + galleryHeight } = this.props; + const {currentIndex} = this.state; + let value: any; let list: any; @@ -229,9 +351,11 @@ export class ImagesField extends React.Component { return (
{ themeCss: wrapperCustomStyle }) )} - style={style} + style={{ + ...style, + ...(displayMode === 'full' && galleryHeight + ? { + '--gallery-height': /^\d+$/.test(String(galleryHeight)) + ? `${galleryHeight}px` + : galleryHeight + } + : {}) + }} + onTouchStart={this.handleTouchStart} + onTouchEnd={this.handleTouchEnd} + onMouseDown={this.handleMouseDown} > {Array.isArray(list) ? (
- {list.map((item: any, index: number) => ( - - ))} + {displayMode === 'full' ? ( + <> +
+ {list.map((item: any, index: number) => ( +
+ {item e.preventDefault()} + /> +
+ ))} +
+ {this.renderImageIndex()} + + ) : ( + list.map((item: any, index: number) => ( + + )) + )}
) : defaultImage ? (
From bf6d6efa7da303eca46278dc7bfaa77def75383d Mon Sep 17 00:00:00 2001 From: tiantiancheng Date: Tue, 21 Jan 2025 17:13:42 +0800 Subject: [PATCH 02/14] =?UTF-8?q?feat:=20=E7=94=BB=E5=BB=8A=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E6=B7=BB=E5=8A=A0=E5=BA=8F=E5=8F=B7=E6=8C=87=E7=A4=BA?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/amis-editor/src/plugin/Images.tsx | 10 +--- packages/amis-ui/scss/components/_images.scss | 48 ++++++++++--------- packages/amis/src/renderers/Images.tsx | 46 ++++++++---------- 3 files changed, 47 insertions(+), 57 deletions(-) diff --git a/packages/amis-editor/src/plugin/Images.tsx b/packages/amis-editor/src/plugin/Images.tsx index 69c696c39f5..7f54d300ad4 100644 --- a/packages/amis-editor/src/plugin/Images.tsx +++ b/packages/amis-editor/src/plugin/Images.tsx @@ -138,7 +138,7 @@ export class ImagesPlugin extends BasePlugin { value: 'thumb' }, { - label: '大图模式', + label: '画廊模式', value: 'full' } ] @@ -169,14 +169,6 @@ export class ImagesPlugin extends BasePlugin { // name: 'showDimensions', // label: '显示图片尺寸' // }), - { - name: 'galleryHeight', - type: 'input-text', - label: '画廊高度', - description: '支持单位: px, rem, vh等', - placeholder: '例如: 400px', - visibleOn: 'this.displayMode === "full"' - }, { name: 'thumbMode', diff --git a/packages/amis-ui/scss/components/_images.scss b/packages/amis-ui/scss/components/_images.scss index 81c83c46a37..a5b5fca4749 100644 --- a/packages/amis-ui/scss/components/_images.scss +++ b/packages/amis-ui/scss/components/_images.scss @@ -35,7 +35,7 @@ position: relative; overflow: hidden; width: 100%; - height: var(--gallery-height, 400px); + height: auto; cursor: grab; &-container { @@ -49,39 +49,43 @@ &-item { flex: 0 0 100%; width: 100%; - height: 100%; margin: 0; display: flex; align-items: center; justify-content: center; background: #fff; + } - .#{$ns}Image-image { - max-width: 100%; - max-height: 100%; - width: auto; - height: auto; - object-fit: contain; - } + &-itemInner { + position: relative; + max-width: 100%; + max-height: 100vh; + } + + .#{$ns}Image-image { + display: block; + max-width: 100%; + height: auto; + object-fit: contain; + } + + &-itemIndex { + position: absolute; + right: 16px; + bottom: 16px; + background: rgba(0, 0, 0, 0.5); + color: white; + padding: 4px 8px; + border-radius: 12px; + font-size: 12px; + z-index: 10; + pointer-events: none; } &.is-swiping { cursor: grabbing; } } - - .Images-index { - position: absolute; - right: 16px; - bottom: 16px; - background: rgba(0, 0, 0, 0.5); - color: white; - padding: 4px 8px; - border-radius: 12px; - font-size: 12px; - z-index: 10; - pointer-events: none; - } } } diff --git a/packages/amis/src/renderers/Images.tsx b/packages/amis/src/renderers/Images.tsx index edb11bf01d7..bc04de251c2 100644 --- a/packages/amis/src/renderers/Images.tsx +++ b/packages/amis/src/renderers/Images.tsx @@ -276,20 +276,12 @@ export class ImagesField extends React.Component { ); } - renderImageIndex() { - const {currentIndex} = this.state; - return ( -
- {currentIndex + 1}/{this.list.length} -
- ); - } - getTransformStyle() { const {currentIndex} = this.state; return { transform: `translateX(-${currentIndex * 100}%)`, - transition: this.state.isSwiping ? 'none' : 'transform 0.3s ease-out' + transition: this.state.isSwiping ? 'none' : 'transform 0.3s ease-out', + height: '100%' }; } @@ -371,11 +363,9 @@ export class ImagesField extends React.Component { )} style={{ ...style, - ...(displayMode === 'full' && galleryHeight + ...(displayMode === 'full' ? { - '--gallery-height': /^\d+$/.test(String(galleryHeight)) - ? `${galleryHeight}px` - : galleryHeight + height: 'auto' } : {}) }} @@ -393,21 +383,25 @@ export class ImagesField extends React.Component { > {list.map((item: any, index: number) => (
- {item e.preventDefault()} - /> +
+ {item e.preventDefault()} + /> +
+ {index + 1}/{list.length} +
+
))}
- {this.renderImageIndex()} ) : ( list.map((item: any, index: number) => ( From b0dfe9b209a69f918f2f64c46524210a359b1f47 Mon Sep 17 00:00:00 2001 From: tiantiancheng Date: Wed, 22 Jan 2025 15:24:09 +0800 Subject: [PATCH 03/14] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E6=BB=91?= =?UTF-8?q?=E5=8A=A8=E5=8A=A8=E7=94=BB=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/amis-ui/scss/components/_images.scss | 3 +- packages/amis/src/renderers/Images.tsx | 51 ++++++++++--------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/packages/amis-ui/scss/components/_images.scss b/packages/amis-ui/scss/components/_images.scss index a5b5fca4749..f81fda86b92 100644 --- a/packages/amis-ui/scss/components/_images.scss +++ b/packages/amis-ui/scss/components/_images.scss @@ -35,7 +35,7 @@ position: relative; overflow: hidden; width: 100%; - height: auto; + height: 100%; cursor: grab; &-container { @@ -47,7 +47,6 @@ } &-item { - flex: 0 0 100%; width: 100%; margin: 0; display: flex; diff --git a/packages/amis/src/renderers/Images.tsx b/packages/amis/src/renderers/Images.tsx index bc04de251c2..ac039dbc4e8 100644 --- a/packages/amis/src/renderers/Images.tsx +++ b/packages/amis/src/renderers/Images.tsx @@ -281,7 +281,9 @@ export class ImagesField extends React.Component { return { transform: `translateX(-${currentIndex * 100}%)`, transition: this.state.isSwiping ? 'none' : 'transform 0.3s ease-out', - height: '100%' + height: '100%', + display: 'flex', + width: `${this.list.length * 100}%` }; } @@ -377,30 +379,33 @@ export class ImagesField extends React.Component {
{displayMode === 'full' ? ( <> -
- {list.map((item: any, index: number) => ( -
-
- {item e.preventDefault()} - /> -
- {index + 1}/{list.length} +
+
+ {list.map((item: any, index: number) => ( +
+
+ {item e.preventDefault()} + /> +
+ {index + 1}/{list.length} +
-
- ))} + ))} +
) : ( From e06ea9044ed7e246ab017913bc7af323f52f5159 Mon Sep 17 00:00:00 2001 From: tiantiancheng Date: Thu, 6 Feb 2025 17:26:48 +0800 Subject: [PATCH 04/14] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E9=9B=86=E5=B1=95=E7=A4=BA=E6=A8=A1=E5=BC=8F=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/zh-CN/components/images.md | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/docs/zh-CN/components/images.md b/docs/zh-CN/components/images.md index b652bdf1a2c..a24b79236b5 100755 --- a/docs/zh-CN/components/images.md +++ b/docs/zh-CN/components/images.md @@ -60,6 +60,45 @@ order: 53 } ``` +## 展示模式 + +通过配置 `displayMode` 可以设置图片集的展示模式,支持以下两种模式: + +- `thumb`: 缩略图模式(默认),将图片以缩略图网格的形式展示 +- `full`: 大图模式,以幻灯片形式展示单张大图,可左右滑动切换 + +```schema +{ + "type": "page", + "data": { + "images": [ + { + "image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692722/4f3cb4202335.jpeg", + "title": "图片1" + }, + { + "image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395692942/d8e4992057f9.jpeg", + "title": "图片2" + }, + { + "image": "https://internal-amis-res.cdn.bcebos.com/images/2020-1/1578395693148/1314a2a3d3f6.jpeg", + "title": "图片3" + } + ] + }, + "body": [ + { + "type": "images", + "source": "${images}", + "displayMode": "full", + "galleryHeight": 300 + } + ] +} +``` + +在 `full` 模式下,可以通过 `galleryHeight` 设置画廊的高度。 + ## 值格式 除了支持纯文本数组以外,也支持对象数组,如: @@ -680,3 +719,5 @@ List 的内容、Card 卡片的内容配置同上。 | thumbRatio | `string` | `1:1` | 预览图比例,可选:`'1:1'`, `'4:3'`, `'16:9'` | | showToolbar | `boolean` | `false` | 放大模式下是否展示图片的工具栏 | `2.2.0` | | toolbarActions | `ImageAction[]` | | 图片工具栏,支持旋转,缩放,默认操作全部开启 | `2.2.0` | +| displayMode | `'thumb' \| 'full'` | `'thumb'` | 展示模式,支持缩略图模式(thumb)和大图模式(full) | +| galleryHeight | `number \| string` | - | 画廊高度,仅在大图模式下生效 | From 9e61dd57be3a364e9c81329949c1f0e995169752 Mon Sep 17 00:00:00 2001 From: tiantiancheng Date: Thu, 6 Feb 2025 19:45:21 +0800 Subject: [PATCH 05/14] =?UTF-8?q?feat:=20=E5=9B=BE=E7=89=87=E9=9B=86?= =?UTF-8?q?=E6=97=A0=E7=BC=9D=E6=BB=91=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/amis/src/renderers/Images.tsx | 131 +++++++++++++++++++++---- 1 file changed, 112 insertions(+), 19 deletions(-) diff --git a/packages/amis/src/renderers/Images.tsx b/packages/amis/src/renderers/Images.tsx index ac039dbc4e8..ffcfccf5f8d 100644 --- a/packages/amis/src/renderers/Images.tsx +++ b/packages/amis/src/renderers/Images.tsx @@ -173,6 +173,7 @@ export class ImagesField extends React.Component { @autobind handleTouchStart(e: React.TouchEvent) { + console.log('handleTouchStart'); if (this.props.displayMode !== 'full') return; this.setState({ @@ -183,30 +184,51 @@ export class ImagesField extends React.Component { @autobind handleTouchEnd(e: React.TouchEvent) { + console.log('handleTouchEnd'); if (!this.state.isSwiping) return; const {currentIndex} = this.state; const deltaX = e.changedTouches[0].clientX - this.state.startX; - const threshold = 50; // 滑动阈值 + const threshold = 50; if (Math.abs(deltaX) > threshold) { - if (deltaX > 0 && currentIndex > 0) { - // 向右滑,显示上一张 - this.setState({currentIndex: currentIndex - 1}); - } else if (deltaX < 0 && currentIndex < this.list.length - 1) { - // 向左滑,显示下一张 - this.setState({currentIndex: currentIndex + 1}); + if (deltaX > 0) { + // 向右滑 + console.log('向右滑'); + this.setState({currentIndex: currentIndex - 1}, () => { + // 如果到达克隆的最后一张,跳转到倒数第二张 + if (currentIndex === 0) { + setTimeout(() => { + this.setState({ + currentIndex: this.list.length - 1, + isSwiping: true + }); + }, 300); + } + }); + } else { + // 向左滑 + console.log('向左滑'); + this.setState({currentIndex: currentIndex + 1}, () => { + // 如果到达克隆的第一张,跳转到第二张 + if (currentIndex === this.list.length - 1) { + setTimeout(() => { + this.setState({ + currentIndex: 0, + isSwiping: true + }); + }, 300); + } + }); } } this.setState({isSwiping: false}); } - componentDidMount() {} - componentWillUnmount() {} - @autobind handleMouseDown(e: React.MouseEvent) { + console.log('handleMouseDown'); if (this.props.displayMode !== 'full') return; // 阻止图片默认的拖拽行为 @@ -223,30 +245,51 @@ export class ImagesField extends React.Component { @autobind handleMouseMove(e: MouseEvent) { + console.log('handleMouseMove'); if (!this.state.isSwiping) return; e.preventDefault(); } @autobind handleMouseUp(e: MouseEvent) { + console.log('handleMouseUp'); if (!this.state.isSwiping) return; const {currentIndex} = this.state; const deltaX = e.clientX - this.state.startX; - const threshold = 50; // 滑动阈值 + const threshold = 50; if (Math.abs(deltaX) > threshold) { - if (deltaX > 0 && currentIndex > 0) { - // 向右滑,显示上一张 - this.setState({currentIndex: currentIndex - 1}); + if (deltaX > 0 && currentIndex >= 0) { + // 向右滑 + this.setState({currentIndex: currentIndex - 1}, () => { + // 如果到达克隆的最后一张,跳转到倒数第二张 + if (currentIndex === -1) { + setTimeout(() => { + this.setState({ + currentIndex: this.list.length - 1, + isSwiping: true + }); + }, 300); + } + }); } else if (deltaX < 0 && currentIndex < this.list.length - 1) { - // 向左滑,显示下一张 - this.setState({currentIndex: currentIndex + 1}); + // 向左滑 + this.setState({currentIndex: currentIndex + 1}, () => { + // 如果到达克隆的第一张,跳转到第二张 + if (currentIndex === this.list.length - 1) { + setTimeout(() => { + this.setState({ + currentIndex: 0, + isSwiping: true + }); + }, 300); + } + }); } } this.setState({isSwiping: false}); - document.removeEventListener('mousemove', this.handleMouseMove); document.removeEventListener('mouseup', this.handleMouseUp); } @@ -278,12 +321,14 @@ export class ImagesField extends React.Component { getTransformStyle() { const {currentIndex} = this.state; + // 考虑到克隆图片,实际索引需要+1 return { - transform: `translateX(-${currentIndex * 100}%)`, + transform: `translateX(-${(currentIndex + 1) * 100}%)`, transition: this.state.isSwiping ? 'none' : 'transform 0.3s ease-out', height: '100%', display: 'flex', - width: `${this.list.length * 100}%` + // 因为首尾各增加一张克隆图片,所以宽度需要增加200% + width: `${(this.list.length + 2) * 100}%` }; } @@ -381,6 +426,31 @@ export class ImagesField extends React.Component { <>
+ {/* 在首尾添加克隆图片 */} +
+
+ {list[list.length e.preventDefault()} + /> +
+ {list.length}/{list.length} +
+
+
+ + {/* 原有图片列表 */} {list.map((item: any, index: number) => (
{
))} + + {/* 添加第一张图片的克隆 */} +
+
+ {list[0]?.title} e.preventDefault()} + /> +
+ 1/{list.length} +
+
+
From d6efebe8e60e8d308e90c84c3bc915004d0f3a22 Mon Sep 17 00:00:00 2001 From: tiantiancheng Date: Thu, 6 Feb 2025 20:07:48 +0800 Subject: [PATCH 06/14] chore: delete log --- packages/amis/src/renderers/Images.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/amis/src/renderers/Images.tsx b/packages/amis/src/renderers/Images.tsx index ffcfccf5f8d..8a3358888a0 100644 --- a/packages/amis/src/renderers/Images.tsx +++ b/packages/amis/src/renderers/Images.tsx @@ -173,7 +173,6 @@ export class ImagesField extends React.Component { @autobind handleTouchStart(e: React.TouchEvent) { - console.log('handleTouchStart'); if (this.props.displayMode !== 'full') return; this.setState({ @@ -184,7 +183,6 @@ export class ImagesField extends React.Component { @autobind handleTouchEnd(e: React.TouchEvent) { - console.log('handleTouchEnd'); if (!this.state.isSwiping) return; const {currentIndex} = this.state; @@ -194,7 +192,6 @@ export class ImagesField extends React.Component { if (Math.abs(deltaX) > threshold) { if (deltaX > 0) { // 向右滑 - console.log('向右滑'); this.setState({currentIndex: currentIndex - 1}, () => { // 如果到达克隆的最后一张,跳转到倒数第二张 if (currentIndex === 0) { @@ -208,7 +205,6 @@ export class ImagesField extends React.Component { }); } else { // 向左滑 - console.log('向左滑'); this.setState({currentIndex: currentIndex + 1}, () => { // 如果到达克隆的第一张,跳转到第二张 if (currentIndex === this.list.length - 1) { @@ -228,7 +224,6 @@ export class ImagesField extends React.Component { @autobind handleMouseDown(e: React.MouseEvent) { - console.log('handleMouseDown'); if (this.props.displayMode !== 'full') return; // 阻止图片默认的拖拽行为 @@ -245,14 +240,12 @@ export class ImagesField extends React.Component { @autobind handleMouseMove(e: MouseEvent) { - console.log('handleMouseMove'); if (!this.state.isSwiping) return; e.preventDefault(); } @autobind handleMouseUp(e: MouseEvent) { - console.log('handleMouseUp'); if (!this.state.isSwiping) return; const {currentIndex} = this.state; From 5fa4022f0ab9deb89ee225a6dc8220610a6313d6 Mon Sep 17 00:00:00 2001 From: tiantiancheng Date: Fri, 7 Feb 2025 10:08:31 +0800 Subject: [PATCH 07/14] =?UTF-8?q?fix:=20pc=20=E7=AB=AF=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/amis/src/renderers/Images.tsx | 43 ++++++++++++++++++++------ 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/packages/amis/src/renderers/Images.tsx b/packages/amis/src/renderers/Images.tsx index 8a3358888a0..6e6143eb898 100644 --- a/packages/amis/src/renderers/Images.tsx +++ b/packages/amis/src/renderers/Images.tsx @@ -253,28 +253,51 @@ export class ImagesField extends React.Component { const threshold = 50; if (Math.abs(deltaX) > threshold) { - if (deltaX > 0 && currentIndex >= 0) { + if (deltaX > 0) { // 向右滑 this.setState({currentIndex: currentIndex - 1}, () => { // 如果到达克隆的最后一张,跳转到倒数第二张 - if (currentIndex === -1) { + if (currentIndex === 0) { + // 等待前一个动画完成 setTimeout(() => { - this.setState({ - currentIndex: this.list.length - 1, - isSwiping: true + // 先禁用动画 + this.setState({isSwiping: true}, () => { + // 在下一帧立即跳转 + requestAnimationFrame(() => { + this.setState( + { + currentIndex: this.list.length - 1 + }, + () => { + // 重新启用动画 + requestAnimationFrame(() => { + this.setState({isSwiping: false}); + }); + } + ); + }); }); }, 300); } }); - } else if (deltaX < 0 && currentIndex < this.list.length - 1) { + } else { // 向左滑 this.setState({currentIndex: currentIndex + 1}, () => { - // 如果到达克隆的第一张,跳转到第二张 if (currentIndex === this.list.length - 1) { setTimeout(() => { - this.setState({ - currentIndex: 0, - isSwiping: true + this.setState({isSwiping: true}, () => { + requestAnimationFrame(() => { + this.setState( + { + currentIndex: 0 + }, + () => { + requestAnimationFrame(() => { + this.setState({isSwiping: false}); + }); + } + ); + }); }); }, 300); } From 726890dd6192536447bbf074ab840468b96e0976 Mon Sep 17 00:00:00 2001 From: tiantiancheng Date: Fri, 7 Feb 2025 10:55:46 +0800 Subject: [PATCH 08/14] =?UTF-8?q?refactor:=20=E7=BB=9F=E4=B8=80=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E7=AB=AF=E5=92=8C=E6=A1=8C=E9=9D=A2=E7=AB=AF=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/amis/src/renderers/Images.tsx | 125 +++++++++---------------- 1 file changed, 42 insertions(+), 83 deletions(-) diff --git a/packages/amis/src/renderers/Images.tsx b/packages/amis/src/renderers/Images.tsx index 6e6143eb898..07ed63ad95a 100644 --- a/packages/amis/src/renderers/Images.tsx +++ b/packages/amis/src/renderers/Images.tsx @@ -172,84 +172,7 @@ export class ImagesField extends React.Component { containerRef = React.createRef(); @autobind - handleTouchStart(e: React.TouchEvent) { - if (this.props.displayMode !== 'full') return; - - this.setState({ - isSwiping: true, - startX: e.touches[0].clientX - }); - } - - @autobind - handleTouchEnd(e: React.TouchEvent) { - if (!this.state.isSwiping) return; - - const {currentIndex} = this.state; - const deltaX = e.changedTouches[0].clientX - this.state.startX; - const threshold = 50; - - if (Math.abs(deltaX) > threshold) { - if (deltaX > 0) { - // 向右滑 - this.setState({currentIndex: currentIndex - 1}, () => { - // 如果到达克隆的最后一张,跳转到倒数第二张 - if (currentIndex === 0) { - setTimeout(() => { - this.setState({ - currentIndex: this.list.length - 1, - isSwiping: true - }); - }, 300); - } - }); - } else { - // 向左滑 - this.setState({currentIndex: currentIndex + 1}, () => { - // 如果到达克隆的第一张,跳转到第二张 - if (currentIndex === this.list.length - 1) { - setTimeout(() => { - this.setState({ - currentIndex: 0, - isSwiping: true - }); - }, 300); - } - }); - } - } - - this.setState({isSwiping: false}); - } - - @autobind - handleMouseDown(e: React.MouseEvent) { - if (this.props.displayMode !== 'full') return; - - // 阻止图片默认的拖拽行为 - e.preventDefault(); - - this.setState({ - isSwiping: true, - startX: e.clientX - }); - - document.addEventListener('mousemove', this.handleMouseMove); - document.addEventListener('mouseup', this.handleMouseUp); - } - - @autobind - handleMouseMove(e: MouseEvent) { - if (!this.state.isSwiping) return; - e.preventDefault(); - } - - @autobind - handleMouseUp(e: MouseEvent) { - if (!this.state.isSwiping) return; - - const {currentIndex} = this.state; - const deltaX = e.clientX - this.state.startX; + private handleSwipe(deltaX: number, currentIndex: number) { const threshold = 50; if (Math.abs(deltaX) > threshold) { @@ -258,18 +181,14 @@ export class ImagesField extends React.Component { this.setState({currentIndex: currentIndex - 1}, () => { // 如果到达克隆的最后一张,跳转到倒数第二张 if (currentIndex === 0) { - // 等待前一个动画完成 setTimeout(() => { - // 先禁用动画 this.setState({isSwiping: true}, () => { - // 在下一帧立即跳转 requestAnimationFrame(() => { this.setState( { currentIndex: this.list.length - 1 }, () => { - // 重新启用动画 requestAnimationFrame(() => { this.setState({isSwiping: false}); }); @@ -304,9 +223,49 @@ export class ImagesField extends React.Component { }); } } + } + + @autobind + handleTouchStart(e: React.TouchEvent) { + if (this.props.displayMode !== 'full') return; + + this.setState({ + isSwiping: true, + startX: e.touches[0].clientX + }); + } + + @autobind + handleTouchEnd(e: React.TouchEvent) { + if (!this.state.isSwiping) return; + + const deltaX = e.changedTouches[0].clientX - this.state.startX; + this.handleSwipe(deltaX, this.state.currentIndex); + this.setState({isSwiping: false}); + } + + @autobind + handleMouseDown(e: React.MouseEvent) { + if (this.props.displayMode !== 'full') return; + + // 阻止图片默认的拖拽行为 + e.preventDefault(); + this.setState({ + isSwiping: true, + startX: e.clientX + }); + + document.addEventListener('mouseup', this.handleMouseUp); + } + + @autobind + handleMouseUp(e: MouseEvent) { + if (!this.state.isSwiping) return; + + const deltaX = e.clientX - this.state.startX; + this.handleSwipe(deltaX, this.state.currentIndex); this.setState({isSwiping: false}); - document.removeEventListener('mousemove', this.handleMouseMove); document.removeEventListener('mouseup', this.handleMouseUp); } From b54fa86ff0c3f32a37be28d4e33503021c82b571 Mon Sep 17 00:00:00 2001 From: tiantiancheng Date: Fri, 7 Feb 2025 11:11:15 +0800 Subject: [PATCH 09/14] refactor: move swipe state from React state to class properties --- packages/amis/src/renderers/Images.tsx | 86 ++++++++++++-------------- 1 file changed, 38 insertions(+), 48 deletions(-) diff --git a/packages/amis/src/renderers/Images.tsx b/packages/amis/src/renderers/Images.tsx index 07ed63ad95a..c8d4b3c1744 100644 --- a/packages/amis/src/renderers/Images.tsx +++ b/packages/amis/src/renderers/Images.tsx @@ -147,8 +147,6 @@ export interface ImagesProps export interface ImagesState { currentIndex: number; - isSwiping: boolean; - startX: number; } export class ImagesField extends React.Component { @@ -163,13 +161,12 @@ export class ImagesField extends React.Component { }; state: ImagesState = { - currentIndex: 0, - isSwiping: false, - startX: 0 + currentIndex: 0 }; + private isSwiping: boolean = false; + private startX: number = 0; list: Array = []; - containerRef = React.createRef(); @autobind private handleSwipe(deltaX: number, currentIndex: number) { @@ -182,19 +179,18 @@ export class ImagesField extends React.Component { // 如果到达克隆的最后一张,跳转到倒数第二张 if (currentIndex === 0) { setTimeout(() => { - this.setState({isSwiping: true}, () => { - requestAnimationFrame(() => { - this.setState( - { - currentIndex: this.list.length - 1 - }, - () => { - requestAnimationFrame(() => { - this.setState({isSwiping: false}); - }); - } - ); - }); + this.isSwiping = true; + requestAnimationFrame(() => { + this.setState( + { + currentIndex: this.list.length - 1 + }, + () => { + requestAnimationFrame(() => { + this.isSwiping = false; + }); + } + ); }); }, 300); } @@ -204,19 +200,18 @@ export class ImagesField extends React.Component { this.setState({currentIndex: currentIndex + 1}, () => { if (currentIndex === this.list.length - 1) { setTimeout(() => { - this.setState({isSwiping: true}, () => { - requestAnimationFrame(() => { - this.setState( - { - currentIndex: 0 - }, - () => { - requestAnimationFrame(() => { - this.setState({isSwiping: false}); - }); - } - ); - }); + this.isSwiping = true; + requestAnimationFrame(() => { + this.setState( + { + currentIndex: 0 + }, + () => { + requestAnimationFrame(() => { + this.isSwiping = false; + }); + } + ); }); }, 300); } @@ -229,19 +224,17 @@ export class ImagesField extends React.Component { handleTouchStart(e: React.TouchEvent) { if (this.props.displayMode !== 'full') return; - this.setState({ - isSwiping: true, - startX: e.touches[0].clientX - }); + this.isSwiping = true; + this.startX = e.touches[0].clientX; } @autobind handleTouchEnd(e: React.TouchEvent) { - if (!this.state.isSwiping) return; + if (!this.isSwiping) return; - const deltaX = e.changedTouches[0].clientX - this.state.startX; + const deltaX = e.changedTouches[0].clientX - this.startX; this.handleSwipe(deltaX, this.state.currentIndex); - this.setState({isSwiping: false}); + this.isSwiping = false; } @autobind @@ -251,21 +244,19 @@ export class ImagesField extends React.Component { // 阻止图片默认的拖拽行为 e.preventDefault(); - this.setState({ - isSwiping: true, - startX: e.clientX - }); + this.isSwiping = true; + this.startX = e.clientX; document.addEventListener('mouseup', this.handleMouseUp); } @autobind handleMouseUp(e: MouseEvent) { - if (!this.state.isSwiping) return; + if (!this.isSwiping) return; - const deltaX = e.clientX - this.state.startX; + const deltaX = e.clientX - this.startX; this.handleSwipe(deltaX, this.state.currentIndex); - this.setState({isSwiping: false}); + this.isSwiping = false; document.removeEventListener('mouseup', this.handleMouseUp); } @@ -299,7 +290,7 @@ export class ImagesField extends React.Component { // 考虑到克隆图片,实际索引需要+1 return { transform: `translateX(-${(currentIndex + 1) * 100}%)`, - transition: this.state.isSwiping ? 'none' : 'transform 0.3s ease-out', + transition: this.isSwiping ? 'none' : 'transform 0.3s ease-out', height: '100%', display: 'flex', // 因为首尾各增加一张克隆图片,所以宽度需要增加200% @@ -365,7 +356,6 @@ export class ImagesField extends React.Component { return (
Date: Fri, 7 Feb 2025 13:46:27 +0800 Subject: [PATCH 10/14] docs: delete galleryHeight --- docs/zh-CN/components/images.md | 6 +----- packages/amis/src/renderers/Images.tsx | 8 +------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/docs/zh-CN/components/images.md b/docs/zh-CN/components/images.md index a24b79236b5..6b93cdd8330 100755 --- a/docs/zh-CN/components/images.md +++ b/docs/zh-CN/components/images.md @@ -90,15 +90,12 @@ order: 53 { "type": "images", "source": "${images}", - "displayMode": "full", - "galleryHeight": 300 + "displayMode": "full" } ] } ``` -在 `full` 模式下,可以通过 `galleryHeight` 设置画廊的高度。 - ## 值格式 除了支持纯文本数组以外,也支持对象数组,如: @@ -720,4 +717,3 @@ List 的内容、Card 卡片的内容配置同上。 | showToolbar | `boolean` | `false` | 放大模式下是否展示图片的工具栏 | `2.2.0` | | toolbarActions | `ImageAction[]` | | 图片工具栏,支持旋转,缩放,默认操作全部开启 | `2.2.0` | | displayMode | `'thumb' \| 'full'` | `'thumb'` | 展示模式,支持缩略图模式(thumb)和大图模式(full) | -| galleryHeight | `number \| string` | - | 画廊高度,仅在大图模式下生效 | diff --git a/packages/amis/src/renderers/Images.tsx b/packages/amis/src/renderers/Images.tsx index c8d4b3c1744..41871a76325 100644 --- a/packages/amis/src/renderers/Images.tsx +++ b/packages/amis/src/renderers/Images.tsx @@ -122,11 +122,6 @@ export interface ImagesSchema extends BaseSchema { * 当前展示图片索引 */ currentIndex?: number; - - /** - * 画廊高度,仅在大图模式下生效 - */ - galleryHeight?: number | string; } export interface ImagesProps @@ -326,8 +321,7 @@ export class ImagesField extends React.Component { env, themeCss, imagesControlClassName, - displayMode, - galleryHeight + displayMode } = this.props; const {currentIndex} = this.state; From 3282de79abb2d3909fb781ca13ba15c4bb15a784 Mon Sep 17 00:00:00 2001 From: tiantiancheng Date: Sat, 8 Feb 2025 10:28:49 +0800 Subject: [PATCH 11/14] =?UTF-8?q?fix(Images):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=BD=AE=E6=92=AD=E5=9B=BE=E5=8A=A8=E7=94=BB=E4=B8=8D=E8=BF=9E?= =?UTF-8?q?=E8=B4=AF=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 通过在 Transition ENTERING 状态时强制触发重排(reflow),确保动画正确执行。 参考了 Carousel 组件的实现方式。 --- packages/amis-ui/scss/components/_images.scss | 13 +- packages/amis/src/renderers/Images.tsx | 251 +++++++++--------- 2 files changed, 140 insertions(+), 124 deletions(-) diff --git a/packages/amis-ui/scss/components/_images.scss b/packages/amis-ui/scss/components/_images.scss index f81fda86b92..538d87cd006 100644 --- a/packages/amis-ui/scss/components/_images.scss +++ b/packages/amis-ui/scss/components/_images.scss @@ -39,26 +39,31 @@ cursor: grab; &-container { - display: flex; + position: relative; width: 100%; height: 100%; - will-change: transform; - user-select: none; + min-height: 300px; } &-item { - width: 100%; margin: 0; display: flex; align-items: center; justify-content: center; background: #fff; + will-change: transform, opacity; + backface-visibility: hidden; } &-itemInner { position: relative; max-width: 100%; max-height: 100vh; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; } .#{$ns}Image-image { diff --git a/packages/amis/src/renderers/Images.tsx b/packages/amis/src/renderers/Images.tsx index 41871a76325..8af7f8570d0 100644 --- a/packages/amis/src/renderers/Images.tsx +++ b/packages/amis/src/renderers/Images.tsx @@ -15,7 +15,12 @@ import Image, {ImageThumbProps, imagePlaceholder} from './Image'; import {autobind, getPropValue} from 'amis-core'; import {BaseSchema, SchemaClassName, SchemaUrlPath} from '../Schema'; import type {ImageToolbarAction} from './Image'; - +import Transition, { + ENTERED, + ENTERING, + EXITING, + EXITED +} from 'react-transition-group/Transition'; /** * 图片集展示控件。 * 文档:https://aisuda.bce.baidu.com/amis/zh-CN/components/images @@ -142,6 +147,7 @@ export interface ImagesProps export interface ImagesState { currentIndex: number; + nextAnimation: string; } export class ImagesField extends React.Component { @@ -156,61 +162,64 @@ export class ImagesField extends React.Component { }; state: ImagesState = { - currentIndex: 0 + currentIndex: 0, + nextAnimation: '' }; private isSwiping: boolean = false; private startX: number = 0; list: Array = []; + wrapperRef: React.RefObject = React.createRef(); + + // 根据当前索引和方向获取下一帧的索引 + @autobind + getFrameId(pos?: string) { + const {currentIndex} = this.state; + const total = this.list.length; + switch (pos) { + case 'prev': + return (currentIndex - 1 + total) % total; + case 'next': + return (currentIndex + 1) % total; + default: + return currentIndex; + } + } + + // 根据方向和动画类型切换到下一帧 + @autobind + async transitFramesTowards(direction: string, nextAnimation: string) { + let {currentIndex} = this.state; + let prevIndex = currentIndex; + + switch (direction) { + case 'left': + currentIndex = this.getFrameId('next'); + nextAnimation = 'slideLeft'; + break; + case 'right': + currentIndex = this.getFrameId('prev'); + nextAnimation = 'slideRight'; + break; + default: + return; + } + + this.setState({currentIndex, nextAnimation}); + } + @autobind - private handleSwipe(deltaX: number, currentIndex: number) { + private handleSwipe(deltaX: number) { const threshold = 50; if (Math.abs(deltaX) > threshold) { if (deltaX > 0) { // 向右滑 - this.setState({currentIndex: currentIndex - 1}, () => { - // 如果到达克隆的最后一张,跳转到倒数第二张 - if (currentIndex === 0) { - setTimeout(() => { - this.isSwiping = true; - requestAnimationFrame(() => { - this.setState( - { - currentIndex: this.list.length - 1 - }, - () => { - requestAnimationFrame(() => { - this.isSwiping = false; - }); - } - ); - }); - }, 300); - } - }); + this.transitFramesTowards('right', 'slideRight'); } else { // 向左滑 - this.setState({currentIndex: currentIndex + 1}, () => { - if (currentIndex === this.list.length - 1) { - setTimeout(() => { - this.isSwiping = true; - requestAnimationFrame(() => { - this.setState( - { - currentIndex: 0 - }, - () => { - requestAnimationFrame(() => { - this.isSwiping = false; - }); - } - ); - }); - }, 300); - } - }); + this.transitFramesTowards('left', 'slideLeft'); } } } @@ -228,7 +237,7 @@ export class ImagesField extends React.Component { if (!this.isSwiping) return; const deltaX = e.changedTouches[0].clientX - this.startX; - this.handleSwipe(deltaX, this.state.currentIndex); + this.handleSwipe(deltaX); this.isSwiping = false; } @@ -250,7 +259,7 @@ export class ImagesField extends React.Component { if (!this.isSwiping) return; const deltaX = e.clientX - this.startX; - this.handleSwipe(deltaX, this.state.currentIndex); + this.handleSwipe(deltaX); this.isSwiping = false; document.removeEventListener('mouseup', this.handleMouseUp); } @@ -350,6 +359,7 @@ export class ImagesField extends React.Component { return (
{ {Array.isArray(list) ? (
{displayMode === 'full' ? ( - <> -
-
- {/* 在首尾添加克隆图片 */} -
-
- {list[list.length e.preventDefault()} - /> -
- {list.length}/{list.length} -
-
-
- - {/* 原有图片列表 */} - {list.map((item: any, index: number) => ( -
-
- {item e.preventDefault()} - /> -
- {index + 1}/{list.length} +
+ {list.map((item: any, index: number) => ( + + {(status: string) => { + if (status === ENTERING) { + this.wrapperRef.current?.childNodes.forEach( + (item: HTMLElement) => item.offsetHeight + ); + } + + const animationStyles: { + [propName: string]: React.CSSProperties; + } = { + [ENTERING]: { + opacity: 1, + transform: 'translateX(0)' + }, + [ENTERED]: { + opacity: 1, + transform: 'translateX(0)' + }, + [EXITING]: { + opacity: 0, + transform: + this.state.nextAnimation === 'slideRight' + ? 'translateX(100%)' + : 'translateX(-100%)' + }, + [EXITED]: { + opacity: 0, + transform: + this.state.nextAnimation === 'slideRight' + ? 'translateX(-100%)' + : 'translateX(100%)' + } + }; + + console.log(animationStyles[status]); + console.log(this.state.nextAnimation); + + return ( +
+
+ {item e.preventDefault()} + /> +
+ {index + 1}/{list.length} +
-
- ))} - - {/* 添加第一张图片的克隆 */} -
-
- {list[0]?.title} e.preventDefault()} - /> -
- 1/{list.length} -
-
-
-
-
- + ); + }} + + ))} +
) : ( list.map((item: any, index: number) => ( Date: Sat, 8 Feb 2025 10:34:09 +0800 Subject: [PATCH 12/14] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/amis/src/renderers/Images.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/amis/src/renderers/Images.tsx b/packages/amis/src/renderers/Images.tsx index 8af7f8570d0..9a0163694d8 100644 --- a/packages/amis/src/renderers/Images.tsx +++ b/packages/amis/src/renderers/Images.tsx @@ -435,9 +435,6 @@ export class ImagesField extends React.Component { } }; - console.log(animationStyles[status]); - console.log(this.state.nextAnimation); - return (
Date: Sat, 8 Feb 2025 10:48:22 +0800 Subject: [PATCH 13/14] =?UTF-8?q?feat(images):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=A4=A7=E5=9B=BE=E6=A8=A1=E5=BC=8F=E4=B8=8B=E7=9A=84=E7=BC=A9?= =?UTF-8?q?=E6=94=BE=E6=8E=A7=E5=88=B6=E5=8F=82=E6=95=B0fullThumbMode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增fullThumbMode参数控制大图模式下的图片缩放方式 - 默认使用cover模式填充容器 - 更新文档说明和示例 --- docs/zh-CN/components/images.md | 9 ++++++++- packages/amis-editor/src/plugin/Images.tsx | 2 +- packages/amis-ui/scss/components/_images.scss | 13 ++++++++++++- packages/amis/src/renderers/Images.tsx | 16 +++++++++++++--- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/docs/zh-CN/components/images.md b/docs/zh-CN/components/images.md index 6b93cdd8330..754bb520b88 100755 --- a/docs/zh-CN/components/images.md +++ b/docs/zh-CN/components/images.md @@ -67,6 +67,11 @@ order: 53 - `thumb`: 缩略图模式(默认),将图片以缩略图网格的形式展示 - `full`: 大图模式,以幻灯片形式展示单张大图,可左右滑动切换 +在大图模式下,可以通过`fullThumbMode`属性来控制图片的缩放模式: + +- `cover`: 保持图片比例,填充整个容器,可能会裁剪部分图片(默认) +- `contain`: 保持图片比例,确保图片完整显示在容器内 + ```schema { "type": "page", @@ -90,7 +95,8 @@ order: 53 { "type": "images", "source": "${images}", - "displayMode": "full" + "displayMode": "full", + "fullThumbMode": "cover" } ] } @@ -717,3 +723,4 @@ List 的内容、Card 卡片的内容配置同上。 | showToolbar | `boolean` | `false` | 放大模式下是否展示图片的工具栏 | `2.2.0` | | toolbarActions | `ImageAction[]` | | 图片工具栏,支持旋转,缩放,默认操作全部开启 | `2.2.0` | | displayMode | `'thumb' \| 'full'` | `'thumb'` | 展示模式,支持缩略图模式(thumb)和大图模式(full) | +| fullThumbMode | `'cover' \| 'contain'` | `'cover'` | 大图模式下的图片缩放模式 | diff --git a/packages/amis-editor/src/plugin/Images.tsx b/packages/amis-editor/src/plugin/Images.tsx index 7f54d300ad4..be052a8fbe2 100644 --- a/packages/amis-editor/src/plugin/Images.tsx +++ b/packages/amis-editor/src/plugin/Images.tsx @@ -138,7 +138,7 @@ export class ImagesPlugin extends BasePlugin { value: 'thumb' }, { - label: '画廊模式', + label: '大图模式', value: 'full' } ] diff --git a/packages/amis-ui/scss/components/_images.scss b/packages/amis-ui/scss/components/_images.scss index 538d87cd006..3264c809ecc 100644 --- a/packages/amis-ui/scss/components/_images.scss +++ b/packages/amis-ui/scss/components/_images.scss @@ -70,7 +70,18 @@ display: block; max-width: 100%; height: auto; - object-fit: contain; + + &--contain { + object-fit: contain; + width: 100%; + height: 100%; + } + + &--cover { + object-fit: cover; + width: 100%; + height: 100%; + } } &-itemIndex { diff --git a/packages/amis/src/renderers/Images.tsx b/packages/amis/src/renderers/Images.tsx index 9a0163694d8..5e8ff011f20 100644 --- a/packages/amis/src/renderers/Images.tsx +++ b/packages/amis/src/renderers/Images.tsx @@ -127,6 +127,11 @@ export interface ImagesSchema extends BaseSchema { * 当前展示图片索引 */ currentIndex?: number; + + /** + * 大图模式下的缩放模式 + */ + fullThumbMode?: 'cover' | 'contain'; } export interface ImagesProps @@ -158,7 +163,8 @@ export class ImagesField extends React.Component { placehoder: '-', thumbMode: 'contain', thumbRatio: '1:1', - displayMode: 'thumb' + displayMode: 'thumb', + fullThumbMode: 'cover' }; state: ImagesState = { @@ -330,7 +336,8 @@ export class ImagesField extends React.Component { env, themeCss, imagesControlClassName, - displayMode + displayMode, + fullThumbMode } = this.props; const {currentIndex} = this.state; @@ -448,7 +455,10 @@ export class ImagesField extends React.Component { >
Date: Sat, 8 Feb 2025 11:14:57 +0800 Subject: [PATCH 14/14] =?UTF-8?q?refactor(images):=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=97=A0=E7=94=A8=E7=9A=84=E5=85=8B=E9=9A=86=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除getTransformStyle方法 - 删除与克隆图片相关的注释 --- packages/amis-ui/scss/components/_images.scss | 162 ++++++++---------- packages/amis/src/renderers/Images.tsx | 13 -- 2 files changed, 71 insertions(+), 104 deletions(-) diff --git a/packages/amis-ui/scss/components/_images.scss b/packages/amis-ui/scss/components/_images.scss index 3264c809ecc..423c8fb8103 100644 --- a/packages/amis-ui/scss/components/_images.scss +++ b/packages/amis-ui/scss/components/_images.scss @@ -11,97 +11,6 @@ var(--image-images-item-marginRight) var(--image-images-item-marginBottom) var(--image-images-item-marginLeft); } - - &-index { - position: absolute; - right: 10px; - bottom: 10px; - background: rgba(0, 0, 0, 0.5); - color: white; - padding: 4px 8px; - border-radius: 12px; - font-size: 12px; - z-index: 1; - } -} - -.#{$ns}ImagesField { - position: relative; - - &--full { - position: relative; - - .#{$ns}Images { - position: relative; - overflow: hidden; - width: 100%; - height: 100%; - cursor: grab; - - &-container { - position: relative; - width: 100%; - height: 100%; - min-height: 300px; - } - - &-item { - margin: 0; - display: flex; - align-items: center; - justify-content: center; - background: #fff; - will-change: transform, opacity; - backface-visibility: hidden; - } - - &-itemInner { - position: relative; - max-width: 100%; - max-height: 100vh; - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; - } - - .#{$ns}Image-image { - display: block; - max-width: 100%; - height: auto; - - &--contain { - object-fit: contain; - width: 100%; - height: 100%; - } - - &--cover { - object-fit: cover; - width: 100%; - height: 100%; - } - } - - &-itemIndex { - position: absolute; - right: 16px; - bottom: 16px; - background: rgba(0, 0, 0, 0.5); - color: white; - padding: 4px 8px; - border-radius: 12px; - font-size: 12px; - z-index: 10; - pointer-events: none; - } - - &.is-swiping { - cursor: grabbing; - } - } - } } .#{$ns}Image { @@ -299,6 +208,77 @@ .#{$ns}ImagesField { position: relative; @include clearfix(); + + &--full { + position: relative; + + .#{$ns}Images { + position: relative; + overflow: hidden; + width: 100%; + height: 100%; + cursor: grab; + + &-container { + position: relative; + width: 100%; + height: 100%; + min-height: 300px; + } + + &-item { + margin: 0; + display: flex; + align-items: center; + justify-content: center; + background: #fff; + will-change: transform, opacity; + backface-visibility: hidden; + } + + &-itemInner { + position: relative; + max-width: 100%; + max-height: 100vh; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } + + .#{$ns}Image-image { + display: block; + max-width: 100%; + height: auto; + + &--contain { + object-fit: contain; + width: 100%; + height: 100%; + } + + &--cover { + object-fit: cover; + width: 100%; + height: 100%; + } + } + + &-itemIndex { + position: absolute; + right: 16px; + bottom: 16px; + background: rgba(0, 0, 0, 0.5); + color: white; + padding: 4px 8px; + border-radius: 12px; + font-size: 12px; + z-index: 10; + pointer-events: none; + } + } + } } .Image-view-icon { diff --git a/packages/amis/src/renderers/Images.tsx b/packages/amis/src/renderers/Images.tsx index 5e8ff011f20..f50f74b649e 100644 --- a/packages/amis/src/renderers/Images.tsx +++ b/packages/amis/src/renderers/Images.tsx @@ -295,19 +295,6 @@ export class ImagesField extends React.Component { ); } - getTransformStyle() { - const {currentIndex} = this.state; - // 考虑到克隆图片,实际索引需要+1 - return { - transform: `translateX(-${(currentIndex + 1) * 100}%)`, - transition: this.isSwiping ? 'none' : 'transform 0.3s ease-out', - height: '100%', - display: 'flex', - // 因为首尾各增加一张克隆图片,所以宽度需要增加200% - width: `${(this.list.length + 2) * 100}%` - }; - } - render() { const { className,