feat(image): 添加预览图加载占位符支持。Add preview image loading placeholder support.
支持通过 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.pull/8322/head
parent
35c1ad9c80
commit
9f465b25a4
|
|
@ -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.
|
||||
|
||||
</docs>
|
||||
|
||||
|
|
@ -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),
|
||||
}"
|
||||
/>
|
||||
<a-image
|
||||
:width="200"
|
||||
src="https://aliyuncdn.antdv.com/logo.png"
|
||||
:preview="{
|
||||
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: true,
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h } from 'vue';
|
||||
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
|
||||
import { theme } from 'ant-design-vue';
|
||||
const { token } = theme.useToken();
|
||||
|
||||
const CustomLoadingComp = h(LoadingOutlined, {
|
||||
style: {
|
||||
fontSize: '256px',
|
||||
color: token.value.colorPrimary,
|
||||
},
|
||||
spin: true,
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*LVQ3R5JjjJEAAA
|
|||
src?: string;
|
||||
maskClassName?: string;
|
||||
current?: number;
|
||||
placeholder?: VNode | boolean;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -193,6 +193,9 @@ export const genImagePreviewStyle: GenerateStyle<ImageToken> = (token: ImageToke
|
|||
transition: `transform ${motionDurationSlow} ${motionEaseOut} 0s`,
|
||||
userSelect: 'none',
|
||||
pointerEvents: 'auto',
|
||||
'&-placeholder': {
|
||||
position: 'absolute',
|
||||
},
|
||||
|
||||
'&-wrapper': {
|
||||
...genBoxStyle(),
|
||||
|
|
@ -214,7 +217,6 @@ export const genImagePreviewStyle: GenerateStyle<ImageToken> = (token: ImageToke
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
[`${previewCls}-moving`]: {
|
||||
[`${previewCls}-preview-img`]: {
|
||||
cursor: 'grabbing',
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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<VNode | boolean>,
|
||||
default: () => true,
|
||||
},
|
||||
rootClassName: String,
|
||||
icons: {
|
||||
type: Object as PropType<PreviewProps['icons']>,
|
||||
|
|
@ -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<ImageStatus> = 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({
|
|||
<img
|
||||
onMousedown={onMouseDown}
|
||||
onDblclick={onDoubleClick}
|
||||
onLoad={() => {
|
||||
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' && (
|
||||
<Spin size="large" class={`${props.prefixCls}-img-placeholder`}></Spin>
|
||||
)}
|
||||
{isCustomPlaceholder.value && status.value === 'loading' && (
|
||||
<div class={`${props.prefixCls}-img-placeholder`}>{props.placeholder}</div>
|
||||
)}
|
||||
</div>
|
||||
{showLeftOrRightSwitches.value && (
|
||||
<div
|
||||
|
|
|
|||
Loading…
Reference in New Issue