From 9f465b25a475fcc0b3304d99099821a4c0c895d8 Mon Sep 17 00:00:00 2001 From: shuoxian Date: Fri, 22 Aug 2025 12:24:52 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(image):=20=E6=B7=BB=E5=8A=A0=E9=A2=84?= =?UTF-8?q?=E8=A7=88=E5=9B=BE=E5=8A=A0=E8=BD=BD=E5=8D=A0=E4=BD=8D=E7=AC=A6?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E3=80=82Add=20preview=20image=20loading=20pl?= =?UTF-8?q?aceholder=20support.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持通过 placeholder 属性自定义预览图的加载占位符,可以是 VNode 或布尔值。当设置为 true 时使用默认的 Spin 组件,设置为 VNode 时则渲染自定义内容。 Support customizing preview image loading placeholder through the placeholder property, which can be a VNode or boolean value. When set to true, it uses the default Spin component; when set to a VNode, it renders custom content. --- components/image/demo/preview-src.vue | 30 +++++++++++++++++++--- components/image/index.zh-CN.md | 1 + components/image/style/index.ts | 4 ++- components/vc-image/src/Image.tsx | 6 ++++- components/vc-image/src/Preview.tsx | 37 +++++++++++++++++++++++++-- 5 files changed, 71 insertions(+), 7 deletions(-) diff --git a/components/image/demo/preview-src.vue b/components/image/demo/preview-src.vue index 2b2d4629d9..ce4570bf8e 100644 --- a/components/image/demo/preview-src.vue +++ b/components/image/demo/preview-src.vue @@ -8,11 +8,11 @@ title: ## zh-CN -可以设置不同的预览图片。 +可以设置不同的预览图片。可以自定义预览图的加载占位符(placeholder)。 ## en-US -You can set different preview image. +You can set different preview image. You can also customize the loading placeholder of preview image with VNode. @@ -21,7 +21,31 @@ You can set different preview image. :width="200" src="https://aliyuncdn.antdv.com/logo.png" :preview="{ - src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + src: 'http://47.100.102.7:11501/admin-api/infra/file/24/get//algorithm/execute/2025/08/20/20210628_iPhoneSE_YL_42_1755674307938_1755674703040.jpg', + placeholder: h(CustomLoadingComp), + }" + /> + + + diff --git a/components/image/index.zh-CN.md b/components/image/index.zh-CN.md index b5a2f148f7..e0b3fe22a9 100644 --- a/components/image/index.zh-CN.md +++ b/components/image/index.zh-CN.md @@ -43,6 +43,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*LVQ3R5JjjJEAAA src?: string; maskClassName?: string; current?: number; + placeholder?: VNode | boolean; } ``` diff --git a/components/image/style/index.ts b/components/image/style/index.ts index 8433f90dd7..cdf0d00a63 100644 --- a/components/image/style/index.ts +++ b/components/image/style/index.ts @@ -193,6 +193,9 @@ export const genImagePreviewStyle: GenerateStyle = (token: ImageToke transition: `transform ${motionDurationSlow} ${motionEaseOut} 0s`, userSelect: 'none', pointerEvents: 'auto', + '&-placeholder': { + position: 'absolute', + }, '&-wrapper': { ...genBoxStyle(), @@ -214,7 +217,6 @@ export const genImagePreviewStyle: GenerateStyle = (token: ImageToke }, }, }, - [`${previewCls}-moving`]: { [`${previewCls}-preview-img`]: { cursor: 'grabbing', diff --git a/components/vc-image/src/Image.tsx b/components/vc-image/src/Image.tsx index 643708dc52..5757c52823 100644 --- a/components/vc-image/src/Image.tsx +++ b/components/vc-image/src/Image.tsx @@ -1,4 +1,4 @@ -import type { CSSProperties, PropType } from 'vue'; +import type { CSSProperties, PropType, VNode } from 'vue'; import { ref, watch, defineComponent, computed, onMounted, onUnmounted } from 'vue'; import isNumber from 'lodash-es/isNumber'; import cn from '../../_util/classNames'; @@ -19,6 +19,8 @@ export type ImagePreviewType = Omit< > & { src?: string; visible?: boolean; + fallback?: string; + placeholder?: VNode | boolean; onVisibleChange?: (value: boolean, prevValue: boolean) => void; getContainer?: GetContainer | false; maskClassName?: string; @@ -79,6 +81,7 @@ const ImageInternal = defineComponent({ onVisibleChange: () => {}, getContainer: undefined, }; + return typeof props.preview === 'object' ? mergeDefaultValue(props.preview, defaultValues) : defaultValues; @@ -288,6 +291,7 @@ const ImageInternal = defineComponent({ onClose={onPreviewClose} mousePosition={mousePosition.value} src={mergedSrc} + placeholder={preview.value.placeholder} alt={alt} getContainer={getPreviewContainer.value} icons={icons} diff --git a/components/vc-image/src/Preview.tsx b/components/vc-image/src/Preview.tsx index 80d03fc70e..0ab3dac667 100644 --- a/components/vc-image/src/Preview.tsx +++ b/components/vc-image/src/Preview.tsx @@ -7,11 +7,15 @@ import { shallowRef, watch, cloneVNode, + ref, + isVNode, + nextTick, } from 'vue'; -import type { VNode, PropType } from 'vue'; - +import type { VNode, Ref, PropType } from 'vue'; +import type { ImageStatus } from './Image'; import classnames from '../../_util/classNames'; import Dialog from '../../vc-dialog'; +import Spin from '../../spin'; import { type IDialogChildProps, dialogPropTypes } from '../../vc-dialog/IDialogPropTypes'; import { getOffset } from '../../vc-util/Dom/css'; import addEventListener from '../../vc-util/Dom/addEventListener'; @@ -49,6 +53,11 @@ export const previewProps = { ...dialogPropTypes(), src: String, alt: String, + fallback: String, + placeholder: { + type: Object as PropType, + default: () => true, + }, rootClassName: String, icons: { type: Object as PropType, @@ -65,7 +74,20 @@ const Preview = defineComponent({ const { rotateLeft, rotateRight, zoomIn, zoomOut, close, left, right, flipX, flipY } = reactive( props.icons, ); + // 判断是否是自定义placeholder + const isCustomPlaceholder = computed(() => isVNode(props.placeholder)); + const isDefaultPlaceholder = computed(() => { + return props.placeholder === true; + }); + const hasPlaceholder = computed(() => isCustomPlaceholder.value || isDefaultPlaceholder.value); + const status: Ref = ref(hasPlaceholder.value ? 'loading' : 'normal'); + watch( + () => props.src, + () => { + status.value = 'loading'; + }, + ); const scale = shallowRef(1); const rotate = shallowRef(0); const flip = reactive({ x: 1, y: 1 }); @@ -386,6 +408,11 @@ const Preview = defineComponent({ { + nextTick(() => { + status.value = 'normal'; + }); + }} ref={imgRef} class={`${props.prefixCls}-img`} src={combinationSrc.value} @@ -396,6 +423,12 @@ const Preview = defineComponent({ }deg)`, }} /> + {isDefaultPlaceholder.value && status.value === 'loading' && ( + + )} + {isCustomPlaceholder.value && status.value === 'loading' && ( +
{props.placeholder}
+ )} {showLeftOrRightSwitches.value && (
Date: Mon, 1 Sep 2025 20:11:35 +0800 Subject: [PATCH 2/2] refactor(Image-Preview): Refactor placeholder from vNode type to () => vNode --- components/image/demo/preview-src.vue | 8 ++++---- components/image/index.en-US.md | 15 ++++++++------- components/image/index.zh-CN.md | 14 +++++++------- components/vc-image/src/Image.tsx | 2 +- components/vc-image/src/Preview.tsx | 13 +++++++++---- 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/components/image/demo/preview-src.vue b/components/image/demo/preview-src.vue index ce4570bf8e..f74bde67eb 100644 --- a/components/image/demo/preview-src.vue +++ b/components/image/demo/preview-src.vue @@ -18,18 +18,18 @@ You can set different preview image. You can also customize the loading placehol