feat(image): add new features (#5479)
* feat(image): add new features * fix: lint error * update test * update code * update docs * perf: reset currentIndex after close * update code * update code * update code * update code * update code * add rootClassName props * fix lintpull/5502/head
parent
32a145a79f
commit
bd87079e12
|
@ -4,6 +4,7 @@ export type KeyboardEventHandler = (e: KeyboardEvent) => void;
|
|||
export type CompositionEventHandler = (e: CompositionEvent) => void;
|
||||
export type ClipboardEventHandler = (e: ClipboardEvent) => void;
|
||||
export type ChangeEventHandler = (e: ChangeEvent) => void;
|
||||
export type WheelEventHandler = (e: WheelEvent) => void;
|
||||
export type ChangeEvent = Event & {
|
||||
target: {
|
||||
value?: string | undefined;
|
||||
|
|
|
@ -2,6 +2,24 @@ import PreviewGroup from '../vc-image/src/PreviewGroup';
|
|||
import { computed, defineComponent } from 'vue';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
|
||||
import RotateLeftOutlined from '@ant-design/icons-vue/RotateLeftOutlined';
|
||||
import RotateRightOutlined from '@ant-design/icons-vue/RotateRightOutlined';
|
||||
import ZoomInOutlined from '@ant-design/icons-vue/ZoomInOutlined';
|
||||
import ZoomOutOutlined from '@ant-design/icons-vue/ZoomOutOutlined';
|
||||
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
|
||||
import LeftOutlined from '@ant-design/icons-vue/LeftOutlined';
|
||||
import RightOutlined from '@ant-design/icons-vue/RightOutlined';
|
||||
|
||||
export const icons = {
|
||||
rotateLeft: <RotateLeftOutlined />,
|
||||
rotateRight: <RotateRightOutlined />,
|
||||
zoomIn: <ZoomInOutlined />,
|
||||
zoomOut: <ZoomOutOutlined />,
|
||||
close: <CloseOutlined />,
|
||||
left: <LeftOutlined />,
|
||||
right: <RightOutlined />,
|
||||
};
|
||||
|
||||
const InternalPreviewGroup = defineComponent({
|
||||
name: 'AImagePreviewGroup',
|
||||
inheritAttrs: false,
|
||||
|
@ -13,6 +31,7 @@ const InternalPreviewGroup = defineComponent({
|
|||
return (
|
||||
<PreviewGroup
|
||||
{...{ ...attrs, ...props }}
|
||||
icons={icons}
|
||||
previewPrefixCls={prefixCls.value}
|
||||
v-slots={slots}
|
||||
></PreviewGroup>
|
||||
|
|
|
@ -3,8 +3,11 @@
|
|||
exports[`renders ./components/image/demo/basic.vue correctly 1`] = `
|
||||
<div class="ant-image" style="width: 200px;"><img class="ant-image-img" src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png">
|
||||
<!---->
|
||||
<!---->
|
||||
<div class="ant-image-mask">
|
||||
<div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
`;
|
||||
|
||||
exports[`renders ./components/image/demo/controlled-preview.vue correctly 1`] = `
|
||||
|
@ -13,7 +16,9 @@ exports[`renders ./components/image/demo/controlled-preview.vue correctly 1`] =
|
|||
</button>
|
||||
<div class="ant-image" style="width: 200px;"><img class="ant-image-img" style="display: none;" src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png">
|
||||
<!---->
|
||||
<!---->
|
||||
<div class="ant-image-mask">
|
||||
<div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
|
@ -22,8 +27,11 @@ exports[`renders ./components/image/demo/controlled-preview.vue correctly 1`] =
|
|||
exports[`renders ./components/image/demo/fallback.vue correctly 1`] = `
|
||||
<div class="ant-image" style="width: 200px; height: 200px;"><img class="ant-image-img" src="https://www.antdv.com/#error">
|
||||
<!---->
|
||||
<!---->
|
||||
<div class="ant-image-mask">
|
||||
<div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
`;
|
||||
|
||||
exports[`renders ./components/image/demo/placeholder.vue correctly 1`] = `
|
||||
|
@ -37,7 +45,9 @@ exports[`renders ./components/image/demo/placeholder.vue correctly 1`] = `
|
|||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-image-mask">
|
||||
<div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
|
@ -52,35 +62,61 @@ exports[`renders ./components/image/demo/placeholder.vue correctly 1`] = `
|
|||
exports[`renders ./components/image/demo/preview-group.vue correctly 1`] = `
|
||||
<div class="ant-image" style="width: 200px;"><img class="ant-image-img" src="https://aliyuncdn.antdv.com/vue.png">
|
||||
<!---->
|
||||
<!---->
|
||||
<div class="ant-image-mask">
|
||||
<div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-image" style="width: 200px;"><img class="ant-image-img" src="https://aliyuncdn.antdv.com/logo.png">
|
||||
<!---->
|
||||
<!---->
|
||||
<div class="ant-image-mask">
|
||||
<div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
`;
|
||||
|
||||
exports[`renders ./components/image/demo/preview-group-visible.vue correctly 1`] = `
|
||||
<div class="ant-image" style="width: 200px;"><img class="ant-image-img" src="https://gw.alipayobjects.com/zos/antfincdn/LlvErxo8H9/photo-1503185912284-5271ff81b9a8.webp">
|
||||
<!---->
|
||||
<!---->
|
||||
<div class="ant-image-mask">
|
||||
<div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div style="display: none;">
|
||||
<div class="ant-image"><img class="ant-image-img" src="https://gw.alipayobjects.com/zos/antfincdn/LlvErxo8H9/photo-1503185912284-5271ff81b9a8.webp">
|
||||
<!---->
|
||||
<!---->
|
||||
<div class="ant-image-mask">
|
||||
<div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-image"><img class="ant-image-img" src="https://gw.alipayobjects.com/zos/antfincdn/cV16ZqzMjW/photo-1473091540282-9b846e7965e3.webp">
|
||||
<!---->
|
||||
<!---->
|
||||
<div class="ant-image-mask">
|
||||
<div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="ant-image"><img class="ant-image-img" src="https://gw.alipayobjects.com/zos/antfincdn/x43I27A55%26/photo-1438109491414-7198515b166b.webp">
|
||||
<!---->
|
||||
<!---->
|
||||
<div class="ant-image-mask">
|
||||
<div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/image/demo/preview-src.vue correctly 1`] = `
|
||||
<div class="ant-image" style="width: 200px;"><img class="ant-image-img" src="https://aliyuncdn.antdv.com/logo.png">
|
||||
<!---->
|
||||
<div class="ant-image-mask">
|
||||
<div class="ant-image-mask-info"><span role="img" aria-label="eye" class="anticon anticon-eye"><svg focusable="false" class="" data-icon="eye" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z"></path></svg></span>Preview</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
`;
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
<fallback />
|
||||
<placeholder />
|
||||
<preview-group />
|
||||
<controlled-preview />
|
||||
<previewGroupVisibleVue />
|
||||
<previewSrc />
|
||||
<controlled-preview />
|
||||
</demo-sort>
|
||||
</template>
|
||||
|
||||
|
@ -13,6 +14,7 @@
|
|||
import Basic from './basic.vue';
|
||||
import Fallback from './fallback.vue';
|
||||
import Placeholder from './placeholder.vue';
|
||||
import previewSrc from './preview-src.vue';
|
||||
import PreviewGroup from './preview-group.vue';
|
||||
import ControlledPreview from './controlled-preview.vue';
|
||||
import previewGroupVisibleVue from './preview-group-visible.vue';
|
||||
|
@ -26,6 +28,7 @@ export default defineComponent({
|
|||
components: {
|
||||
Basic,
|
||||
Fallback,
|
||||
previewSrc,
|
||||
Placeholder,
|
||||
PreviewGroup,
|
||||
ControlledPreview,
|
||||
|
|
|
@ -18,7 +18,7 @@ Preview a collection from one image.
|
|||
|
||||
<template>
|
||||
<a-image
|
||||
:preview="{ visible }"
|
||||
:preview="{ visible: false }"
|
||||
:width="200"
|
||||
src="https://gw.alipayobjects.com/zos/antfincdn/LlvErxo8H9/photo-1503185912284-5271ff81b9a8.webp"
|
||||
@click="visible = true"
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<docs>
|
||||
---
|
||||
order: 4
|
||||
title:
|
||||
zh-CN: 自定义预览图片
|
||||
en-US: Custom preview image
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
可以设置不同的预览图片。
|
||||
|
||||
## en-US
|
||||
|
||||
You can set different preview image.
|
||||
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<a-image
|
||||
:width="200"
|
||||
src="https://aliyuncdn.antdv.com/logo.png"
|
||||
:preview="{
|
||||
src: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
export default defineComponent({});
|
||||
</script>
|
|
@ -22,7 +22,9 @@ Previewable image.
|
|||
| placeholder | Load placeholder, use default placeholder when set `true` | boolean \| slot | - | 2.0.0 |
|
||||
| preview | preview config, disabled when `false` | boolean \| [previewType](#previewType) | true | 2.0.0 |
|
||||
| src | Image path | string | - | 2.0.0 |
|
||||
| previewMask | custom mask | slot | - | 3.2.0 |
|
||||
| width | Image width | string \| number | - | 2.0.0 |
|
||||
| onError | Load failed callback | (event: Event) => void | - | 3.2.0 |
|
||||
|
||||
### previewType
|
||||
|
||||
|
@ -31,6 +33,9 @@ Previewable image.
|
|||
visible?: boolean;
|
||||
onVisibleChange?: (visible, prevVisible) => void;
|
||||
getContainer?: string | HTMLElement | (() => HTMLElement);
|
||||
src?: string;
|
||||
maskClassName?: string;
|
||||
current?: number;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import type { App, ExtractPropTypes, ImgHTMLAttributes, Plugin } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import ImageInternal from '../vc-image';
|
||||
import { imageProps } from '../vc-image/src/Image';
|
||||
import defaultLocale from '../locale/en_US';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
import PreviewGroup from './PreviewGroup';
|
||||
import PreviewGroup, { icons } from './PreviewGroup';
|
||||
import EyeOutlined from '@ant-design/icons-vue/EyeOutlined';
|
||||
import { getTransitionName } from '../_util/transition';
|
||||
|
||||
export type ImageProps = Partial<
|
||||
ExtractPropTypes<ReturnType<typeof imageProps>> &
|
||||
|
@ -14,12 +17,46 @@ const Image = defineComponent<ImageProps>({
|
|||
inheritAttrs: false,
|
||||
props: imageProps() as any,
|
||||
setup(props, { slots, attrs }) {
|
||||
const { prefixCls } = useConfigInject('image', props);
|
||||
const { prefixCls, rootPrefixCls, configProvider } = useConfigInject('image', props);
|
||||
|
||||
const mergedPreview = computed(() => {
|
||||
const { preview } = props;
|
||||
|
||||
if (preview === false) {
|
||||
return preview;
|
||||
}
|
||||
const _preview = typeof preview === 'object' ? preview : {};
|
||||
|
||||
return {
|
||||
icons,
|
||||
..._preview,
|
||||
transitionName: getTransitionName(rootPrefixCls.value, 'zoom', _preview.transitionName),
|
||||
maskTransitionName: getTransitionName(
|
||||
rootPrefixCls.value,
|
||||
'fade',
|
||||
_preview.maskTransitionName,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return () => {
|
||||
const imageLocale = configProvider.locale?.Image || defaultLocale.Image;
|
||||
|
||||
return (
|
||||
<ImageInternal
|
||||
{...{ ...attrs, ...props, prefixCls: prefixCls.value }}
|
||||
v-slots={slots}
|
||||
preview={mergedPreview.value}
|
||||
v-slots={{
|
||||
...slots,
|
||||
previewMask:
|
||||
slots.previewMask ??
|
||||
(() => (
|
||||
<div class={`${prefixCls.value}-mask-info`}>
|
||||
<EyeOutlined />
|
||||
{imageLocale?.preview}
|
||||
</div>
|
||||
)),
|
||||
}}
|
||||
></ImageInternal>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -23,7 +23,9 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/D1dXz9PZqa/image.svg
|
|||
| placeholder | 加载占位, 为 `true` 时使用默认占位 | boolean \| slot | - | 2.0.0 |
|
||||
| preview | 预览参数,为 `false` 时禁用 | boolean \| [previewType](#previewType) | true | 2.0.0 |
|
||||
| src | 图片地址 | string | - | 2.0.0 |
|
||||
| previewMask | 自定义 mask | slot | - | 3.2.0 |
|
||||
| width | 图像宽度 | string \| number | - | 2.0.0 |
|
||||
| onError | 加载错误回调 | (event: Event) => void | - | 3.2.0 |
|
||||
|
||||
### previewType
|
||||
|
||||
|
@ -32,6 +34,9 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/D1dXz9PZqa/image.svg
|
|||
visible?: boolean;
|
||||
onVisibleChange?: (visible, prevVisible) => void;
|
||||
getContainer: string | HTMLElement | (() => HTMLElement);
|
||||
src?: string;
|
||||
maskClassName?: string;
|
||||
current?: number;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -143,6 +143,7 @@ export default defineComponent({
|
|||
maskAnimation,
|
||||
zIndex,
|
||||
wrapClassName,
|
||||
rootClassName,
|
||||
wrapStyle,
|
||||
closable,
|
||||
maskProps,
|
||||
|
@ -154,7 +155,7 @@ export default defineComponent({
|
|||
} = props;
|
||||
const { style, class: className } = attrs;
|
||||
return (
|
||||
<div class={`${prefixCls}-root`} {...pickAttrs(props, { data: true })}>
|
||||
<div class={[`${prefixCls}-root`, rootClassName]} {...pickAttrs(props, { data: true })}>
|
||||
<Mask
|
||||
prefixCls={prefixCls}
|
||||
visible={mask && visible}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
|
||||
function dialogPropTypes() {
|
||||
export function dialogPropTypes() {
|
||||
return {
|
||||
keyboard: { type: Boolean, default: undefined },
|
||||
mask: { type: Boolean, default: undefined },
|
||||
|
@ -25,6 +25,7 @@ function dialogPropTypes() {
|
|||
maskStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
|
||||
prefixCls: String,
|
||||
wrapClassName: String,
|
||||
rootClassName: String,
|
||||
width: [String, Number],
|
||||
height: [String, Number],
|
||||
zIndex: Number,
|
||||
|
|
|
@ -1,21 +1,29 @@
|
|||
import type { ImgHTMLAttributes, CSSProperties, PropType } from 'vue';
|
||||
import { ref, watch, defineComponent, computed, onMounted } from 'vue';
|
||||
import { ref, watch, defineComponent, computed, onMounted, onUnmounted } from 'vue';
|
||||
import isNumber from 'lodash-es/isNumber';
|
||||
import cn from '../../_util/classNames';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import { getOffset } from '../../vc-util/Dom/css';
|
||||
|
||||
import type { MouseEventHandler } from './Preview';
|
||||
import useMergedState from '../../_util/hooks/useMergedState';
|
||||
import Preview from './Preview';
|
||||
|
||||
import type { MouseEventHandler } from '../../_util/EventInterface';
|
||||
import PreviewGroup, { context } from './PreviewGroup';
|
||||
|
||||
import type { IDialogChildProps } from '../../vc-dialog/IDialogPropTypes';
|
||||
export type GetContainer = string | HTMLElement | (() => HTMLElement);
|
||||
export interface ImagePreviewType {
|
||||
import type { PreviewProps } from './Preview';
|
||||
|
||||
export type ImagePreviewType = Omit<
|
||||
IDialogChildProps,
|
||||
'mask' | 'visible' | 'closable' | 'prefixCls' | 'onClose' | 'afterClose' | 'wrapClassName'
|
||||
> & {
|
||||
src?: string;
|
||||
visible?: boolean;
|
||||
onVisibleChange?: (value: boolean, prevValue: boolean) => void;
|
||||
getContainer?: GetContainer | false;
|
||||
}
|
||||
maskClassName?: string;
|
||||
icons?: PreviewProps['icons'];
|
||||
};
|
||||
|
||||
export interface ImagePropsType extends Omit<ImgHTMLAttributes, 'placeholder' | 'onClick'> {
|
||||
// Original
|
||||
|
@ -27,11 +35,14 @@ export interface ImagePropsType extends Omit<ImgHTMLAttributes, 'placeholder' |
|
|||
placeholder?: boolean;
|
||||
fallback?: string;
|
||||
preview?: boolean | ImagePreviewType;
|
||||
onClick?: MouseEventHandler;
|
||||
onError?: HTMLImageElement['onerror'];
|
||||
}
|
||||
export const imageProps = () => ({
|
||||
src: String,
|
||||
wrapperClassName: String,
|
||||
wrapperStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
|
||||
rootClassName: String,
|
||||
prefixCls: String,
|
||||
previewPrefixCls: String,
|
||||
placeholder: PropTypes.any,
|
||||
|
@ -40,10 +51,17 @@ export const imageProps = () => ({
|
|||
type: [Boolean, Object] as PropType<boolean | ImagePreviewType>,
|
||||
default: true as boolean | ImagePreviewType,
|
||||
},
|
||||
onClick: {
|
||||
type: Function as PropType<MouseEventHandler>,
|
||||
},
|
||||
onError: {
|
||||
type: Function as PropType<HTMLImageElement['onerror']>,
|
||||
},
|
||||
});
|
||||
type ImageStatus = 'normal' | 'error' | 'loading';
|
||||
export type ImageProps = Partial<ReturnType<typeof imageProps>>;
|
||||
export type ImageStatus = 'normal' | 'error' | 'loading';
|
||||
|
||||
const mergeDefaultValue = <T extends object>(obj: T, defaultValues: object): T => {
|
||||
export const mergeDefaultValue = <T extends object>(obj: T, defaultValues: object): T => {
|
||||
const res = { ...obj };
|
||||
Object.keys(defaultValues).forEach(key => {
|
||||
if (obj[key] === undefined) {
|
||||
|
@ -57,11 +75,11 @@ const ImageInternal = defineComponent({
|
|||
name: 'Image',
|
||||
inheritAttrs: false,
|
||||
props: imageProps(),
|
||||
emits: ['click'],
|
||||
emits: ['click', 'error'],
|
||||
setup(props, { attrs, slots, emit }) {
|
||||
const prefixCls = computed(() => props.prefixCls);
|
||||
const previewPrefixCls = computed(() => `${prefixCls.value}-preview`);
|
||||
const preview = computed(() => {
|
||||
const preview = computed<ImagePreviewType>(() => {
|
||||
const defaultValues = {
|
||||
visible: undefined,
|
||||
onVisibleChange: () => {},
|
||||
|
@ -75,16 +93,21 @@ const ImageInternal = defineComponent({
|
|||
() => (props.placeholder && props.placeholder !== true) || slots.placeholder,
|
||||
);
|
||||
const previewVisible = computed(() => preview.value.visible);
|
||||
const onPreviewVisibleChange = computed(() => preview.value.onVisibleChange);
|
||||
const getPreviewContainer = computed(() => preview.value.getContainer);
|
||||
|
||||
const isControlled = computed(() => previewVisible.value !== undefined);
|
||||
const isShowPreview = ref(!!previewVisible.value);
|
||||
watch(previewVisible, () => {
|
||||
isShowPreview.value = !!previewVisible.value;
|
||||
|
||||
const onPreviewVisibleChange = (val, preval) => {
|
||||
preview.value.onVisibleChange?.(val, preval);
|
||||
};
|
||||
const [isShowPreview, setShowPreview] = useMergedState(!!previewVisible.value, {
|
||||
value: previewVisible,
|
||||
onChange: onPreviewVisibleChange,
|
||||
});
|
||||
watch(previewVisible, val => {
|
||||
setShowPreview(Boolean(val));
|
||||
});
|
||||
watch(isShowPreview, (val, preVal) => {
|
||||
onPreviewVisibleChange.value(val, preVal);
|
||||
onPreviewVisibleChange(val, preVal);
|
||||
});
|
||||
const status = ref<ImageStatus>(isCustomPlaceholder.value ? 'loading' : 'normal');
|
||||
watch(
|
||||
|
@ -108,13 +131,15 @@ const ImageInternal = defineComponent({
|
|||
const onLoad = () => {
|
||||
status.value = 'normal';
|
||||
};
|
||||
const onError = () => {
|
||||
const onError = (e: Event) => {
|
||||
status.value = 'error';
|
||||
emit('error', e);
|
||||
};
|
||||
|
||||
const onPreview: MouseEventHandler = e => {
|
||||
if (!isControlled.value) {
|
||||
const { left, top } = getOffset(e.target);
|
||||
|
||||
if (isPreviewGroup.value) {
|
||||
setCurrent(currentId.value);
|
||||
setGroupMousePosition({
|
||||
|
@ -131,13 +156,13 @@ const ImageInternal = defineComponent({
|
|||
if (isPreviewGroup.value) {
|
||||
setGroupShowPreview(true);
|
||||
} else {
|
||||
isShowPreview.value = true;
|
||||
setShowPreview(true);
|
||||
}
|
||||
emit('click', e);
|
||||
};
|
||||
|
||||
const onPreviewClose = () => {
|
||||
isShowPreview.value = false;
|
||||
setShowPreview(false);
|
||||
if (!isControlled.value) {
|
||||
mousePosition.value = null;
|
||||
}
|
||||
|
@ -163,7 +188,7 @@ const ImageInternal = defineComponent({
|
|||
return () => {};
|
||||
}
|
||||
|
||||
unRegister = registerImage(currentId.value, props.src);
|
||||
unRegister = registerImage(currentId.value, props.src, canPreview.value);
|
||||
|
||||
if (!canPreview.value) {
|
||||
unRegister();
|
||||
|
@ -172,13 +197,21 @@ const ImageInternal = defineComponent({
|
|||
{ flush: 'post', immediate: true },
|
||||
);
|
||||
});
|
||||
onUnmounted(unRegister);
|
||||
const toSizePx = (l: number | string) => {
|
||||
if (isNumber(l)) return l + 'px';
|
||||
return l;
|
||||
};
|
||||
return () => {
|
||||
const { prefixCls, wrapperClassName, fallback, src, preview, placeholder, wrapperStyle } =
|
||||
props;
|
||||
const {
|
||||
prefixCls,
|
||||
wrapperClassName,
|
||||
fallback,
|
||||
src: imgSrc,
|
||||
placeholder,
|
||||
wrapperStyle,
|
||||
rootClassName,
|
||||
} = props;
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
|
@ -191,11 +224,12 @@ const ImageInternal = defineComponent({
|
|||
class: cls,
|
||||
style,
|
||||
} = attrs as ImgHTMLAttributes;
|
||||
const wrappperClass = cn(prefixCls, wrapperClassName, {
|
||||
const { icons, maskClassName, src: previewSrc, ...dialogProps } = preview.value;
|
||||
|
||||
const wrappperClass = cn(prefixCls, wrapperClassName, rootClassName, {
|
||||
[`${prefixCls}-error`]: isError.value,
|
||||
});
|
||||
const mergedSrc = isError.value && fallback ? fallback : src;
|
||||
const previewMask = slots.previewMask && slots.previewMask();
|
||||
const mergedSrc = isError.value && fallback ? fallback : previewSrc ?? imgSrc;
|
||||
const imgCommonProps = {
|
||||
crossorigin,
|
||||
decoding,
|
||||
|
@ -215,12 +249,13 @@ const ImageInternal = defineComponent({
|
|||
...(style as CSSProperties),
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
class={wrappperClass}
|
||||
onClick={
|
||||
preview && !isError.value
|
||||
canPreview.value
|
||||
? onPreview
|
||||
: e => {
|
||||
emit('click', e);
|
||||
|
@ -238,7 +273,7 @@ const ImageInternal = defineComponent({
|
|||
? {
|
||||
src: fallback,
|
||||
}
|
||||
: { onLoad, onError, src })}
|
||||
: { onLoad, onError, src: imgSrc })}
|
||||
ref={img}
|
||||
/>
|
||||
|
||||
|
@ -248,12 +283,13 @@ const ImageInternal = defineComponent({
|
|||
</div>
|
||||
)}
|
||||
{/* Preview Click Mask */}
|
||||
{previewMask && canPreview.value && (
|
||||
<div class={`${prefixCls}-mask`}>{previewMask}</div>
|
||||
{slots.previewMask && canPreview.value && (
|
||||
<div class={[`${prefixCls}-mask`, maskClassName]}>{slots.previewMask()}</div>
|
||||
)}
|
||||
</div>
|
||||
{!isPreviewGroup.value && canPreview.value && (
|
||||
<Preview
|
||||
{...dialogProps}
|
||||
aria-hidden={!isShowPreview.value}
|
||||
visible={isShowPreview.value}
|
||||
prefixCls={previewPrefixCls.value}
|
||||
|
@ -262,6 +298,8 @@ const ImageInternal = defineComponent({
|
|||
src={mergedSrc}
|
||||
alt={alt}
|
||||
getContainer={getPreviewContainer.value}
|
||||
icons={icons}
|
||||
rootClassName={rootClassName}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -1,47 +1,65 @@
|
|||
import { computed, defineComponent, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||
import RotateLeftOutlined from '@ant-design/icons-vue/RotateLeftOutlined';
|
||||
import RotateRightOutlined from '@ant-design/icons-vue/RotateRightOutlined';
|
||||
import ZoomInOutlined from '@ant-design/icons-vue/ZoomInOutlined';
|
||||
import ZoomOutOutlined from '@ant-design/icons-vue/ZoomOutOutlined';
|
||||
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
|
||||
import LeftOutlined from '@ant-design/icons-vue/LeftOutlined';
|
||||
import RightOutlined from '@ant-design/icons-vue/RightOutlined';
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
cloneVNode,
|
||||
} from 'vue';
|
||||
import type { VNode, PropType } from 'vue';
|
||||
|
||||
import classnames from '../../_util/classNames';
|
||||
import Dialog from '../../vc-dialog';
|
||||
import getIDialogPropTypes from '../../vc-dialog/IDialogPropTypes';
|
||||
import { type IDialogChildProps, dialogPropTypes } from '../../vc-dialog/IDialogPropTypes';
|
||||
import { getOffset } from '../../vc-util/Dom/css';
|
||||
import addEventListener from '../../vc-util/Dom/addEventListener';
|
||||
import { warning } from '../../vc-util/warning';
|
||||
import useFrameSetState from './hooks/useFrameSetState';
|
||||
import getFixScaleEleTransPosition from './getFixScaleEleTransPosition';
|
||||
import type { MouseEventHandler, WheelEventHandler } from '../../_util/EventInterface';
|
||||
|
||||
import { context } from './PreviewGroup';
|
||||
|
||||
const IDialogPropTypes = getIDialogPropTypes();
|
||||
export type MouseEventHandler = (payload: MouseEvent) => void;
|
||||
|
||||
export interface PreviewProps extends Omit<typeof IDialogPropTypes, 'onClose'> {
|
||||
export interface PreviewProps extends Omit<IDialogChildProps, 'onClose' | 'mask'> {
|
||||
onClose?: (e: Element) => void;
|
||||
src?: string;
|
||||
alt?: string;
|
||||
rootClassName?: string;
|
||||
icons?: {
|
||||
rotateLeft?: VNode;
|
||||
rotateRight?: VNode;
|
||||
zoomIn?: VNode;
|
||||
zoomOut?: VNode;
|
||||
close?: VNode;
|
||||
left?: VNode;
|
||||
right?: VNode;
|
||||
};
|
||||
}
|
||||
|
||||
const initialPosition = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
const PreviewType = {
|
||||
export const previewProps = {
|
||||
...dialogPropTypes(),
|
||||
src: String,
|
||||
alt: String,
|
||||
...IDialogPropTypes,
|
||||
rootClassName: String,
|
||||
icons: {
|
||||
type: Object as PropType<PreviewProps['icons']>,
|
||||
default: () => ({} as PreviewProps['icons']),
|
||||
},
|
||||
};
|
||||
const Preview = defineComponent({
|
||||
name: 'Preview',
|
||||
inheritAttrs: false,
|
||||
props: PreviewType,
|
||||
props: previewProps,
|
||||
emits: ['close', 'afterClose'],
|
||||
setup(props, { emit, attrs }) {
|
||||
const { rotateLeft, rotateRight, zoomIn, zoomOut, close, left, right } = reactive(props.icons);
|
||||
|
||||
const scale = ref(1);
|
||||
const rotate = ref(0);
|
||||
const [position, setPosition] = useFrameSetState<{
|
||||
|
@ -65,22 +83,22 @@ const Preview = defineComponent({
|
|||
const isMoving = ref(false);
|
||||
const groupContext = context.inject();
|
||||
const { previewUrls, current, isPreviewGroup, setCurrent } = groupContext;
|
||||
const previewGroupCount = computed(() => Object.keys(previewUrls).length);
|
||||
const previewUrlsKeys = computed(() => Object.keys(previewUrls));
|
||||
const currentPreviewIndex = computed(() =>
|
||||
previewUrlsKeys.value.indexOf(String(current.value)),
|
||||
);
|
||||
const combinationSrc = computed(() =>
|
||||
isPreviewGroup.value ? previewUrls[current.value] : props.src,
|
||||
);
|
||||
const previewGroupCount = computed(() => previewUrls.value.size);
|
||||
const previewUrlsKeys = computed(() => Array.from(previewUrls.value.keys()));
|
||||
const currentPreviewIndex = computed(() => previewUrlsKeys.value.indexOf(current.value));
|
||||
const combinationSrc = computed(() => {
|
||||
return isPreviewGroup.value ? previewUrls.value.get(current.value) : props.src;
|
||||
});
|
||||
const showLeftOrRightSwitches = computed(
|
||||
() => isPreviewGroup.value && previewGroupCount.value > 1,
|
||||
);
|
||||
const lastWheelZoomDirection = ref({ wheelDirection: 0 });
|
||||
|
||||
const onAfterClose = () => {
|
||||
scale.value = 1;
|
||||
rotate.value = 0;
|
||||
setPosition(initialPosition);
|
||||
emit('afterClose');
|
||||
};
|
||||
|
||||
const onZoomIn = () => {
|
||||
|
@ -106,7 +124,7 @@ const Preview = defineComponent({
|
|||
// Without this mask close will abnormal
|
||||
event.stopPropagation();
|
||||
if (currentPreviewIndex.value > 0) {
|
||||
setCurrent(previewUrlsKeys.value[String(currentPreviewIndex.value - 1)]);
|
||||
setCurrent(previewUrlsKeys.value[currentPreviewIndex.value - 1]);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -115,7 +133,7 @@ const Preview = defineComponent({
|
|||
// Without this mask close will abnormal
|
||||
event.stopPropagation();
|
||||
if (currentPreviewIndex.value < previewGroupCount.value - 1) {
|
||||
setCurrent(previewUrlsKeys.value[String(currentPreviewIndex.value + 1)]);
|
||||
setCurrent(previewUrlsKeys.value[currentPreviewIndex.value + 1]);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -126,28 +144,28 @@ const Preview = defineComponent({
|
|||
const iconClassName = `${props.prefixCls}-operations-icon`;
|
||||
const tools = [
|
||||
{
|
||||
icon: CloseOutlined,
|
||||
icon: close,
|
||||
onClick: onClose,
|
||||
type: 'close',
|
||||
},
|
||||
{
|
||||
icon: ZoomInOutlined,
|
||||
icon: zoomIn,
|
||||
onClick: onZoomIn,
|
||||
type: 'zoomIn',
|
||||
},
|
||||
{
|
||||
icon: ZoomOutOutlined,
|
||||
icon: zoomOut,
|
||||
onClick: onZoomOut,
|
||||
type: 'zoomOut',
|
||||
disabled: computed(() => scale.value === 1),
|
||||
},
|
||||
{
|
||||
icon: RotateRightOutlined,
|
||||
icon: rotateRight,
|
||||
onClick: onRotateRight,
|
||||
type: 'rotateRight',
|
||||
},
|
||||
{
|
||||
icon: RotateLeftOutlined,
|
||||
icon: rotateLeft,
|
||||
onClick: onRotateLeft,
|
||||
type: 'rotateLeft',
|
||||
},
|
||||
|
@ -175,6 +193,8 @@ const Preview = defineComponent({
|
|||
};
|
||||
|
||||
const onMouseDown: MouseEventHandler = event => {
|
||||
// Only allow main button
|
||||
if (event.button !== 0) return;
|
||||
event.preventDefault();
|
||||
// Without this mask close will abnormal
|
||||
event.stopPropagation();
|
||||
|
@ -193,6 +213,14 @@ const Preview = defineComponent({
|
|||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onWheelMove: WheelEventHandler = event => {
|
||||
if (!props.visible) return;
|
||||
event.preventDefault();
|
||||
const wheelDirection = event.deltaY;
|
||||
lastWheelZoomDirection.value = { wheelDirection };
|
||||
};
|
||||
|
||||
let removeListeners = () => {};
|
||||
onMounted(() => {
|
||||
watch(
|
||||
|
@ -204,6 +232,9 @@ const Preview = defineComponent({
|
|||
|
||||
const onMouseUpListener = addEventListener(window, 'mouseup', onMouseUp, false);
|
||||
const onMouseMoveListener = addEventListener(window, 'mousemove', onMouseMove, false);
|
||||
const onScrollWheelListener = addEventListener(window, 'wheel', onWheelMove, {
|
||||
passive: false,
|
||||
});
|
||||
|
||||
try {
|
||||
// Resolve if in iframe lost event
|
||||
|
@ -225,6 +256,7 @@ const Preview = defineComponent({
|
|||
removeListeners = () => {
|
||||
onMouseUpListener.remove();
|
||||
onMouseMoveListener.remove();
|
||||
onScrollWheelListener.remove();
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (onTopMouseUpListener) onTopMouseUpListener.remove();
|
||||
|
@ -240,6 +272,8 @@ const Preview = defineComponent({
|
|||
});
|
||||
|
||||
return () => {
|
||||
const { visible, prefixCls, rootClassName } = props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
{...attrs}
|
||||
|
@ -247,11 +281,12 @@ const Preview = defineComponent({
|
|||
maskTransitionName="fade"
|
||||
closable={false}
|
||||
keyboard
|
||||
prefixCls={props.prefixCls}
|
||||
prefixCls={prefixCls}
|
||||
onClose={onClose}
|
||||
afterClose={onAfterClose}
|
||||
visible={props.visible}
|
||||
visible={visible}
|
||||
wrapClassName={wrapClassName}
|
||||
rootClassName={rootClassName}
|
||||
getContainer={props.getContainer}
|
||||
>
|
||||
<ul class={`${props.prefixCls}-operations`}>
|
||||
|
@ -263,7 +298,7 @@ const Preview = defineComponent({
|
|||
onClick={onClick}
|
||||
key={type}
|
||||
>
|
||||
<IconType class={iconClassName} />
|
||||
{cloneVNode(IconType, { class: iconClassName })}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
@ -291,7 +326,7 @@ const Preview = defineComponent({
|
|||
})}
|
||||
onClick={onSwitchLeft}
|
||||
>
|
||||
<LeftOutlined />
|
||||
{left}
|
||||
</div>
|
||||
)}
|
||||
{showLeftOrRightSwitches.value && (
|
||||
|
@ -302,7 +337,7 @@ const Preview = defineComponent({
|
|||
})}
|
||||
onClick={onSwitchRight}
|
||||
>
|
||||
<RightOutlined />
|
||||
{right}
|
||||
</div>
|
||||
)}
|
||||
</Dialog>
|
||||
|
|
|
@ -1,19 +1,39 @@
|
|||
import type { Ref } from 'vue';
|
||||
import { ref, provide, defineComponent, inject, reactive } from 'vue';
|
||||
import type { PropType, Ref, ComputedRef } from 'vue';
|
||||
import { ref, provide, defineComponent, inject, watch, reactive, computed, watchEffect } from 'vue';
|
||||
import { type ImagePreviewType, mergeDefaultValue } from './Image';
|
||||
import Preview from './Preview';
|
||||
import type { PreviewProps } from './Preview';
|
||||
|
||||
export interface PreviewGroupPreview
|
||||
extends Omit<ImagePreviewType, 'icons' | 'mask' | 'maskClassName'> {
|
||||
/**
|
||||
* If Preview the show img index
|
||||
* @default 0
|
||||
*/
|
||||
current?: number;
|
||||
}
|
||||
|
||||
export interface GroupConsumerProps {
|
||||
previewPrefixCls?: string;
|
||||
icons?: PreviewProps['icons'];
|
||||
preview?: boolean | PreviewGroupPreview;
|
||||
}
|
||||
|
||||
interface PreviewUrl {
|
||||
url: string;
|
||||
canPreview: boolean;
|
||||
}
|
||||
|
||||
export interface GroupConsumerValue extends GroupConsumerProps {
|
||||
isPreviewGroup?: Ref<boolean | undefined>;
|
||||
previewUrls: Record<number, string>;
|
||||
setPreviewUrls: (previewUrls: Record<number, string>) => void;
|
||||
previewUrls: ComputedRef<Map<number, string>>;
|
||||
setPreviewUrls: (id: number, url: string, canPreview?: boolean) => void;
|
||||
current: Ref<number>;
|
||||
setCurrent: (current: number) => void;
|
||||
setShowPreview: (isShowPreview: boolean) => void;
|
||||
setMousePosition: (mousePosition: null | { x: number; y: number }) => void;
|
||||
registerImage: (id: number, url: string) => () => void;
|
||||
registerImage: (id: number, url: string, canPreview?: boolean) => () => void;
|
||||
rootClassName?: string;
|
||||
}
|
||||
const previewGroupContext = Symbol('previewGroupContext');
|
||||
export const context = {
|
||||
|
@ -23,13 +43,14 @@ export const context = {
|
|||
inject: () => {
|
||||
return inject<GroupConsumerValue>(previewGroupContext, {
|
||||
isPreviewGroup: ref(false),
|
||||
previewUrls: reactive({}),
|
||||
previewUrls: computed(() => new Map()),
|
||||
setPreviewUrls: () => {},
|
||||
current: ref(null),
|
||||
setCurrent: () => {},
|
||||
setShowPreview: () => {},
|
||||
setMousePosition: () => {},
|
||||
registerImage: null,
|
||||
rootClassName: '',
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@ -37,14 +58,56 @@ export const context = {
|
|||
const Group = defineComponent({
|
||||
name: 'PreviewGroup',
|
||||
inheritAttrs: false,
|
||||
props: { previewPrefixCls: String },
|
||||
props: {
|
||||
previewPrefixCls: String,
|
||||
preview: {
|
||||
type: [Boolean, Object] as PropType<boolean | ImagePreviewType>,
|
||||
default: true as boolean | ImagePreviewType,
|
||||
},
|
||||
icons: {
|
||||
type: Object as PropType<PreviewProps['icons']>,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const previewUrls = reactive<Record<number, string>>({});
|
||||
const preview = computed<PreviewGroupPreview>(() => {
|
||||
const defaultValues = {
|
||||
visible: undefined,
|
||||
onVisibleChange: () => {},
|
||||
getContainer: undefined,
|
||||
current: 0,
|
||||
};
|
||||
return typeof props.preview === 'object'
|
||||
? mergeDefaultValue(props.preview, defaultValues)
|
||||
: defaultValues;
|
||||
});
|
||||
const previewUrls = reactive<Map<number, PreviewUrl>>(new Map());
|
||||
const current = ref<number>();
|
||||
const isShowPreview = ref<boolean>(false);
|
||||
|
||||
const previewVisible = computed(() => preview.value.visible);
|
||||
const onPreviewVisibleChange = computed(() => preview.value.onVisibleChange);
|
||||
const getPreviewContainer = computed(() => preview.value.getContainer);
|
||||
|
||||
const isShowPreview = ref(!!previewVisible.value);
|
||||
|
||||
const mousePosition = ref<{ x: number; y: number }>(null);
|
||||
const setPreviewUrls = (val: Record<number, string>) => {
|
||||
Object.assign(previewUrls, val);
|
||||
const isControlled = computed(() => previewVisible.value !== undefined);
|
||||
const previewUrlsKeys = computed(() => Array.from(previewUrls.keys()));
|
||||
const currentControlledKey = computed(() => previewUrlsKeys.value[preview.value.current]);
|
||||
const canPreviewUrls = computed(
|
||||
() =>
|
||||
new Map<number, string>(
|
||||
Array.from(previewUrls)
|
||||
.filter(([, { canPreview }]) => !!canPreview)
|
||||
.map(([id, { url }]) => [id, url]),
|
||||
),
|
||||
);
|
||||
|
||||
const setPreviewUrls = (id: number, url: string, canPreview = true) => {
|
||||
previewUrls.set(id, {
|
||||
url,
|
||||
canPreview,
|
||||
});
|
||||
};
|
||||
const setCurrent = (val: number) => {
|
||||
current.value = val;
|
||||
|
@ -55,21 +118,54 @@ const Group = defineComponent({
|
|||
const setShowPreview = (val: boolean) => {
|
||||
isShowPreview.value = val;
|
||||
};
|
||||
const registerImage = (id: number, url: string) => {
|
||||
previewUrls[id] = url;
|
||||
|
||||
return () => {
|
||||
delete previewUrls[id];
|
||||
const registerImage = (id: number, url: string, canPreview = true) => {
|
||||
const unRegister = () => {
|
||||
previewUrls.delete(id);
|
||||
};
|
||||
previewUrls.set(id, {
|
||||
url,
|
||||
canPreview,
|
||||
});
|
||||
return unRegister;
|
||||
};
|
||||
|
||||
const onPreviewClose = (e: any) => {
|
||||
e?.stopPropagation();
|
||||
isShowPreview.value = false;
|
||||
mousePosition.value = null;
|
||||
};
|
||||
|
||||
watch(previewVisible, () => {
|
||||
isShowPreview.value = !!previewVisible.value;
|
||||
});
|
||||
watch(isShowPreview, (val, preVal) => {
|
||||
onPreviewVisibleChange.value(val, preVal);
|
||||
});
|
||||
watch(
|
||||
currentControlledKey,
|
||||
val => {
|
||||
setCurrent(val);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
flush: 'post',
|
||||
},
|
||||
);
|
||||
watchEffect(
|
||||
() => {
|
||||
if (!isShowPreview.value && isControlled.value) {
|
||||
setCurrent(currentControlledKey.value);
|
||||
}
|
||||
},
|
||||
{
|
||||
flush: 'post',
|
||||
},
|
||||
);
|
||||
|
||||
context.provide({
|
||||
isPreviewGroup: ref(true),
|
||||
previewUrls,
|
||||
previewUrls: canPreviewUrls,
|
||||
setPreviewUrls,
|
||||
current,
|
||||
setCurrent,
|
||||
|
@ -77,17 +173,22 @@ const Group = defineComponent({
|
|||
setMousePosition,
|
||||
registerImage,
|
||||
});
|
||||
|
||||
return () => {
|
||||
const { ...dialogProps } = preview.value;
|
||||
return (
|
||||
<>
|
||||
{slots.default && slots.default()}
|
||||
<Preview
|
||||
{...dialogProps}
|
||||
ria-hidden={!isShowPreview.value}
|
||||
visible={isShowPreview.value}
|
||||
prefixCls={props.previewPrefixCls}
|
||||
onClose={onPreviewClose}
|
||||
mousePosition={mousePosition.value}
|
||||
src={previewUrls[current.value]}
|
||||
src={canPreviewUrls.value.get(current.value)}
|
||||
icons={props.icons}
|
||||
getContainer={getPreviewContainer.value}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue