From bc67af2901869a83ed6f4df3b27ccad0756757ba Mon Sep 17 00:00:00 2001 From: typistZxd <1046866318@qq.com> Date: Thu, 6 Jan 2022 20:37:59 +0800 Subject: [PATCH] feat(comp:image): add image and imageViewer components fix #698 --- .../components/config/src/defaultConfig.ts | 13 +- packages/components/config/src/types.ts | 12 +- .../__snapshots__/image.spec.ts.snap | 44 +-- .../__snapshots__/imageViewer.spec.ts.snap | 3 + .../components/image/__tests__/image.spec.ts | 187 +++++------- .../image/__tests__/imageViewer.spec.ts | 209 +++++++++++++ packages/components/image/demo/Basic.md | 8 +- packages/components/image/demo/Basic.vue | 30 +- packages/components/image/demo/Fallback.md | 9 - packages/components/image/demo/Fallback.vue | 16 - packages/components/image/demo/ImageAttrs.md | 14 + packages/components/image/demo/ImageAttrs.vue | 14 + packages/components/image/demo/ImageSlot.md | 14 + packages/components/image/demo/ImageSlot.vue | 23 ++ packages/components/image/demo/ImageViewer.md | 14 + .../components/image/demo/ImageViewer.vue | 18 ++ packages/components/image/demo/Preview.md | 12 +- packages/components/image/demo/Preview.vue | 2 +- packages/components/image/demo/Zoom.md | 14 + packages/components/image/demo/Zoom.vue | 20 ++ packages/components/image/docs/Design.en.md | 3 + packages/components/image/docs/Design.zh.md | 5 +- packages/components/image/docs/Index.en.md | 29 ++ packages/components/image/docs/Index.zh.md | 35 ++- packages/components/image/index.ts | 18 +- packages/components/image/src/Image.tsx | 160 ++++++++++ packages/components/image/src/Image.vue | 93 ------ packages/components/image/src/ImageViewer.tsx | 43 +++ packages/components/image/src/ImgPreview.vue | 83 ----- .../src/component/ImageViewerContent.tsx | 286 ++++++++++++++++++ packages/components/image/src/types.ts | 51 +++- packages/components/image/style/image.less | 78 +++++ packages/components/image/style/index.less | 75 ----- .../image/style/themes/default.less | 4 +- .../components/image/style/themes/default.ts | 1 - .../image/style/themes/default.variable.less | 31 +- packages/components/image/style/viewer.less | 56 ++++ packages/components/index.ts | 3 +- .../components/style/variable/prefix.less | 2 +- packages/components/types.d.ts | 3 +- packages/site/public/images/example/css.png | Bin 0 -> 14877 bytes packages/site/public/images/example/html.png | Bin 0 -> 13313 bytes packages/site/public/images/example/js.png | Bin 0 -> 44629 bytes .../site/public/images/example/nodejs.png | Bin 0 -> 21808 bytes 44 files changed, 1231 insertions(+), 504 deletions(-) create mode 100644 packages/components/image/__tests__/__snapshots__/imageViewer.spec.ts.snap create mode 100644 packages/components/image/__tests__/imageViewer.spec.ts delete mode 100644 packages/components/image/demo/Fallback.md delete mode 100644 packages/components/image/demo/Fallback.vue create mode 100644 packages/components/image/demo/ImageAttrs.md create mode 100644 packages/components/image/demo/ImageAttrs.vue create mode 100644 packages/components/image/demo/ImageSlot.md create mode 100644 packages/components/image/demo/ImageSlot.vue create mode 100644 packages/components/image/demo/ImageViewer.md create mode 100644 packages/components/image/demo/ImageViewer.vue create mode 100644 packages/components/image/demo/Zoom.md create mode 100644 packages/components/image/demo/Zoom.vue create mode 100644 packages/components/image/docs/Design.en.md create mode 100644 packages/components/image/docs/Index.en.md create mode 100644 packages/components/image/src/Image.tsx delete mode 100644 packages/components/image/src/Image.vue create mode 100644 packages/components/image/src/ImageViewer.tsx delete mode 100644 packages/components/image/src/ImgPreview.vue create mode 100644 packages/components/image/src/component/ImageViewerContent.tsx create mode 100644 packages/components/image/style/image.less delete mode 100644 packages/components/image/style/index.less create mode 100644 packages/components/image/style/viewer.less create mode 100644 packages/site/public/images/example/css.png create mode 100644 packages/site/public/images/example/html.png create mode 100644 packages/site/public/images/example/js.png create mode 100644 packages/site/public/images/example/nodejs.png diff --git a/packages/components/config/src/defaultConfig.ts b/packages/components/config/src/defaultConfig.ts index e4caf21bb..f9aaea9a9 100644 --- a/packages/components/config/src/defaultConfig.ts +++ b/packages/components/config/src/defaultConfig.ts @@ -26,6 +26,7 @@ import type { GlobalConfig, IconConfig, ImageConfig, + ImageViewerConfig, InputConfig, InputNumberConfig, ListConfig, @@ -232,10 +233,13 @@ const collapse: CollapseConfig = { } const image: ImageConfig = { - width: 100, - height: 100, - fallback: - 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTI4cHgiIGhlaWdodD0iMTI4cHgiIHZpZXdCb3g9IjAgMCAxMjggMTI4IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCA1OCAoODQ2NjMpIC0gaHR0cHM6Ly9za2V0Y2guY29tIC0tPgogICAgPHRpdGxlPjEyODwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxkZWZzPgogICAgICAgIDxsaW5lYXJHcmFkaWVudCB4MT0iNTAlIiB5MT0iMCUiIHgyPSI1MCUiIHkyPSI5OS42Mjc4NTA1JSIgaWQ9ImxpbmVhckdyYWRpZW50LTEiPgogICAgICAgICAgICA8c3RvcCBzdG9wLWNvbG9yPSIjMDBBQ0ZGIiBvZmZzZXQ9IjAlIj48L3N0b3A+CiAgICAgICAgICAgIDxzdG9wIHN0b3AtY29sb3I9IiMzMzY2RkYiIG9mZnNldD0iMTAwJSI+PC9zdG9wPgogICAgICAgIDwvbGluZWFyR3JhZGllbnQ+CiAgICAgICAgPGxpbmVhckdyYWRpZW50IHgxPSI1MCUiIHkxPSIwJSIgeDI9IjUwJSIgeTI9IjEwMCUiIGlkPSJsaW5lYXJHcmFkaWVudC0yIj4KICAgICAgICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iIzFEQjgzRiIgb2Zmc2V0PSIwJSI+PC9zdG9wPgogICAgICAgICAgICA8c3RvcCBzdG9wLWNvbG9yPSIjNzJEMTNEIiBvZmZzZXQ9IjEwMCUiPjwvc3RvcD4KICAgICAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPC9kZWZzPgogICAgPGcgaWQ9IjEyOCIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPHBhdGggZD0iTTQ0LjQ2NzMxMjYsNjIuMjMyMTcwNSBMNzAuMTI5ODAxNSw4My43NjU1NTU0IEM2MC4xODk3NTEyLDk1LjYxMTY0NiA0Mi41Mjg1OTQ2LDk3LjE1Njc5NyAzMC42ODI1MDQsODcuMjE2NzQ2NyBMMjguMjQ4MTQ5Miw4NS4xNzM4OTc1IEMyMS4zMTIxMTA4LDc5LjMwODkyOTQgMTQuMzM1NDQzOSw3My40NTQ4NzE4IDcuMzE4MTQ4NDYsNjcuNjExNzI0NiBMOS4yNDY1MTEyOSw2NS4zMTM1OTEzIEMxMy44NDgzODY0LDU5LjgyOTI5MDEgMjAuMzAzNzUwNyw1Ni44MTc3Nzk2IDI2LjkyNzcxNzYsNTYuNDI2NDY4MyBMMjguMzQ5MTA1Nyw1Ni4zODI2OTk1IEMzNC4wNDAwNjg2LDU2LjM2ODg1MDQgMzkuNzY2NDgzLDU4LjI4NzcwNjEgNDQuNDY3MzEyNiw2Mi4yMzIxNzA1IFoiIGlkPSLot6/lvoQiIGZpbGw9IiMyMDRFRDkiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNOTUuNDM4MDk4NCw0NS4xMzQ1MDkgTDk3LjMzMTY2NTYsNDUuMjI3OTkzMiBMOTguNzAxMDU1OSw0NS4zNjI3MDcyIEwxMDAuNTE2MTI5LDQ1LjYyOTI5MTYgTDEwMS44OTEyODcsNDUuODk5NDE2NCBMMTAzLjQzNjU4NSw0Ni4yNzUwNzE5IEwxMDUuMTY5NDEsNDYuNzkwNDA1MSBMMTA2LjU4NzY2NSw0Ny4yODk1OTM3IEwxMDguMjEyNTY1LDQ3Ljk1MjAwNzQgTDEwOC45MzE3Niw0OC4yNzc2NDAxIEwxMDguOTMxNzYsNDguMjc3NjQwMSBMMTEwLjM1Mzg5OCw0OC45ODM2MzEgTDExMS44MDUxMjgsNDkuNzk0NjA0NSBMMTEzLjQ0MzE5OSw1MC44MzAxOTgxIEwxMTQuNjUzNDk2LDUxLjY4NTY5MTIgTDExNS44MjE1MjEsNTIuNTkyMTI3NSBMMTE3LjE4ODUzNyw1My43NjU3NjYgTDExOC40NzMyOTksNTQuOTk1MDg2NCBMMTE5LjUzMzEzLDU2LjExNTU4MDYgTDEyMC4wODg2Miw1Ni43NDY0NjgzIEwxMjAuMDg4NjIsNTYuNzQ2NDY4MyBMMTIxLjIwODAwMyw1OC4xMjIyMDA1IEwxMjIuMTMwNDY5LDU5LjM3NzQ1NiBMNzIuMjk3NDUzMyw5NC4yNzA5MDkyIEM2Ny41NzI1ODc4LDk3LjU3OTI5NTcgNjIuMTMzNTU1NCw5OS4zMTAyNzggNTYuNjY4NDkzMSw5OS40OTg0MDA0IEw1NS4wMjg4ODE3LDk5LjUwODU4NTggQzQ4LjQ3MjgxNzUsOTkuMzY0NDYwNyA0MS45NzgzNjIyLDk3LjAwMzU5MTYgMzYuNzM0NjE2NSw5Mi40ODU2NzA5IEwzMi43NjgwODE2LDg5LjAzMzg2MiBMMzIuNzY4MDgxNiw4OS4wMzM4NjIgTDI3LjYzNjUwMzksODQuNjQ2ODk0IEw3NS42Mzg0ODQ5LDUxLjE0MzQ2MzQgQzc3LjQ1OTY4NTEsNDkuODcyMzM5NyA3OS4zNjM0NjM3LDQ4LjgwNzg1NjQgODEuMzIxOTc2Miw0Ny45NDUwODIgTDgzLjI5NzM3NzksNDcuMTQ5NDc5OSBDODQuMzIzMzMxMSw0Ni43NzQwNzgyIDg1LjM2MTE5MzEsNDYuNDUxNzgxNSA4Ni40MDcyNTc2LDQ2LjE4MTg4MTUgTDg3Ljk4MjA0MzEsNDUuODE2MjQwMyBMODkuNjQ0MTg2Niw0NS41MTY1NyBMOTEuMzg1OTMwMyw0NS4yOTQ4NTUxIEw5My4wNTA0MjI4LDQ1LjE2OTM0MjMgQzkzLjg0NjY4MzIsNDUuMTI5Mjc1NyA5NC42NDMxMDY0LDQ1LjExNzc1ODggOTUuNDM4MDk4NCw0NS4xMzQ1MDkgWiIgaWQ9Iui3r+W+hCIgZmlsbD0idXJsKCNsaW5lYXJHcmFkaWVudC0xKSI+PC9wYXRoPgogICAgICAgIDxwYXRoIGQ9Ik01MS4zODg4NTIsMzIuNDY5NDc1OSBMNjMuNjQ1NTYzMSw0Mi43NTQwNzc2IEM1OC42NzU1MzgsNDguNjc3MTIyOSA0OS44NDQ5NTk3LDQ5LjQ0OTY5ODQgNDMuOTIxOTE0NCw0NC40Nzk2NzMzIEwzMi44MTQyNywzNS4xNTkyNTMgTDMzLjc3ODQ1MTQsMzQuMDEwMTg2MyBDMzguMjE1OTczOCwyOC43MjE3NTMgNDYuMTAwNDE4NywyOC4wMzE5NTM0IDUxLjM4ODg1MiwzMi40Njk0NzU5IFoiIGlkPSLot6/lvoQiIGZpbGw9IiMwMzc4MkEiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNNzIuMDY0NTA2MiwyNi4xMjg0NDA2IEw3My4yNjYzMDc5LDI2LjIyMjA0MTMgTDc0LjM3NzU0MTYsMjYuMzk3MDkgTDc1LjU3NjI3OTIsMjYuNjg0OTIyNCBMNzYuNDc4NTYyMywyNi45NzMxMDM2IEw3Ny41MTY0NTg5LDI3LjM4NjMzNDcgTDc3LjUxNjQ1ODksMjcuMzg2MzM0NyBMNzguMjAwNjU1MywyNy43MTA0NDczIEw3OS4zMDc1MTExLDI4LjMzMTQwODYgTDc5LjcwNDIsMjguNTg1OTgwNSBMODAuMjkxNTU3OCwyOC45OTc0NTY2IEw4MC44MDkzMjg3LDI5LjM5NzU4MjggTDgxLjIxODA3NjgsMjkuNzQwODE0MyBMODEuNzcyNzI0MywzMC4yNDk2ODA3IEw4Mi4yMTE2Mzk2LDMwLjY5MTk2MzIgTDgyLjIxMTYzOTYsMzAuNjkxOTYzMiBMODIuNjM0NzgxOCwzMS4xNTYxOTk0IEw4My4wOTIyMjcyLDMxLjcwNjIzMjEgTDgzLjYxOTAyNjMsMzIuNDEzNDQ1NSBMNjMuMTYzMzEwOCw0Ni43MzY2OTE3IEM2MC45ODg3ODEsNDguMjU5MzEzOSA1OC41MTE3ODE3LDQ5LjExMzY3NjggNTYuMDAwNjA2Myw0OS4zMTMzMzE2IEw1NC43NDM0MTI4LDQ5LjM1ODY2MjggQzUxLjM4OTkyNjQsNDkuMzM0Mzg5NyA0OC4wNTUxNzcxLDQ4LjE1MDIwNjEgNDUuMzc1MTE5NSw0NS44MzgyMzM0IEw0Mi4zNzc0OTk1LDQzLjI1MjMxMyBMNjMuMDcwNjgxNiwyOC43ODMyMDI2IEM2My42NzAxMjExLDI4LjM2NDA2MTggNjQuMjg5ODcxMywyNy45OTU2NDMzIDY0LjkyNDgzNTYsMjcuNjc3MDQ2OCBMNjUuOTUxNDUxMywyNy4yMTAyOTkxIEw2NS45NTE0NTEzLDI3LjIxMDI5OTEgTDY2LjU3MDYwOTYsMjYuOTczMjE1NyBMNjcuNzY3NzYyNywyNi42MDI1NDE2IEw2Ny43Njc3NjI3LDI2LjYwMjU0MTYgTDY4Ljk4MjU5NjQsMjYuMzM3MjA5MiBMNjkuNzk2ODcyMSwyNi4yMTg1ODE3IEw3MC4yMTY0MDg1LDI2LjE3NTQ3ODUgQzcwLjgzMjMzODgsMjYuMTIxMDkyMyA3MS40NDk2MzQ3LDI2LjEwNTYzNjEgNzIuMDY0NTA2MiwyNi4xMjg0NDA2IFoiIGlkPSLot6/lvoQiIGZpbGw9InVybCgjbGluZWFyR3JhZGllbnQtMikiPjwvcGF0aD4KICAgIDwvZz4KPC9zdmc+', + preview: true, +} + +const imageViewer: ImageViewerConfig = { + loop: true, + maskClosable: true, + zoom: [0.5, 2], } const statistic: StatisticConfig = { @@ -413,6 +417,7 @@ export const defaultConfig: GlobalConfig = { list, collapse, image, + imageViewer, statistic, table, tooltip, diff --git a/packages/components/config/src/types.ts b/packages/components/config/src/types.ts index ddebefd6f..201acb571 100644 --- a/packages/components/config/src/types.ts +++ b/packages/components/config/src/types.ts @@ -226,9 +226,14 @@ export interface CollapseConfig { } export interface ImageConfig { - width: string | number - height: string | number - fallback: string + preview: boolean +} + +export interface ImageViewerConfig { + loop: boolean + maskClosable: boolean + zoom: number[] + target?: PortalTargetType } export interface NumFormatted { @@ -436,6 +441,7 @@ export interface GlobalConfig { list: ListConfig collapse: CollapseConfig image: ImageConfig + imageViewer: ImageViewerConfig statistic: StatisticConfig table: TableConfig tooltip: TooltipConfig diff --git a/packages/components/image/__tests__/__snapshots__/image.spec.ts.snap b/packages/components/image/__tests__/__snapshots__/image.spec.ts.snap index 54061e86f..e253ee5b6 100644 --- a/packages/components/image/__tests__/__snapshots__/image.spec.ts.snap +++ b/packages/components/image/__tests__/__snapshots__/image.spec.ts.snap @@ -1,43 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Image render alt work 1`] = ` -"
\\"demo\\" - -
" -`; - -exports[`Image render height work 1`] = ` -"
\\"\\" - -
" -`; - -exports[`Image render objectFit work 1`] = ` -"
\\"\\" - -
" -`; - -exports[`Image render preview work 1`] = ` -"
\\"\\" - -
" -`; - -exports[`Image render src work 1`] = ` -"
\\"\\" - -
" -`; - -exports[`Image render width work 1`] = ` -"
\\"\\" - -
" -`; - exports[`Image render work 1`] = ` -"
\\"\\" - +"
+
+ + + +
" `; diff --git a/packages/components/image/__tests__/__snapshots__/imageViewer.spec.ts.snap b/packages/components/image/__tests__/__snapshots__/imageViewer.spec.ts.snap new file mode 100644 index 000000000..eebd6fd0d --- /dev/null +++ b/packages/components/image/__tests__/__snapshots__/imageViewer.spec.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ImageViewer render work 1`] = `""`; diff --git a/packages/components/image/__tests__/image.spec.ts b/packages/components/image/__tests__/image.spec.ts index 7ad589c68..cfe97bbbf 100644 --- a/packages/components/image/__tests__/image.spec.ts +++ b/packages/components/image/__tests__/image.spec.ts @@ -1,130 +1,109 @@ -import { MountingOptions, mount } from '@vue/test-utils' +import { MountingOptions, flushPromises, mount } from '@vue/test-utils' +import { h } from 'vue' import { renderWork } from '@tests' -import IxImage from '../src/Image.vue' +import Image from '../src/Image' +import ImageViewer from '../src/ImageViewer' import { ImageProps } from '../src/types' describe('Image', () => { - const ImageMount = (options?: MountingOptions>) => mount(IxImage, { ...options }) + const ImageMount = (options?: MountingOptions>) => { + const { props, ...rest } = options ?? {} + const src = '/icons/logo.svg' - renderWork(IxImage) + return mount(Image, { ...({ ...rest, props: { src, ...props } } as MountingOptions) }) + } - test('render src work', async () => { - const wrapper = ImageMount({ - props: { - src: 'https://cdn.jsdelivr.net/gh/danranvm/image-hosting/images/idux.jpg', - }, - }) - expect(wrapper.html()).toMatchSnapshot() - await wrapper.setProps({ src: 'https://cdn.jsdelivr.net/gh/danranvm/image-hosting/images/vue.png' }) - }) - test('render width work', async () => { - const wrapper = ImageMount({ - props: { - width: '100px', - src: 'https://cdn.jsdelivr.net/gh/danranvm/image-hosting/images/idux.jpg', - }, - }) - expect(wrapper.html()).toMatchSnapshot() - await wrapper.setProps({ width: '200px' }) - expect(wrapper.find('img').attributes()['style']).toMatch('width: 200px') - await wrapper.setProps({ width: null }) + renderWork(Image, { props: { src: '/icons/logo.svg' } }) - expect(wrapper.find('img').attributes()['style']).toMatch(`width: 100px`) - }) - test('render height work', async () => { - const wrapper = ImageMount({ - props: { - height: '100px', - src: 'https://cdn.jsdelivr.net/gh/danranvm/image-hosting/images/idux.jpg', - }, - }) + test('src work', async () => { + const testSrcA = '/a' + const testSrcB = '/b' + const wrapper = ImageMount({ props: { src: testSrcA } }) + await flushPromises() + + expect(wrapper.find('.ix-image-inner').attributes('src')).toBe(testSrcA) + + await wrapper.setProps({ src: testSrcB }) - expect(wrapper.html()).toMatchSnapshot() - await wrapper.setProps({ height: '200px' }) - expect(wrapper.find('img').attributes()['style']).toMatch('height: 200px') - await wrapper.setProps({ height: null }) - expect(wrapper.find('img').attributes()['style']).toMatch('height: 100px') + expect(wrapper.find('.ix-image-inner').attributes('src')).toBe(testSrcB) }) - test('render alt work', async () => { - const wrapper = ImageMount({ - props: { - alt: 'demo', - src: 'https://cdn.jsdelivr.net/gh/danranvm/image-hosting/images/idux.jpg', - }, - }) - expect(wrapper.html()).toMatchSnapshot() - expect(wrapper.find('img').attributes()['alt']).toEqual('demo') + test('preview work', async () => { + const wrapper = ImageMount() + await flushPromises() + await wrapper.find('.ix-image-inner').trigger('load') + + expect(wrapper.find('.ix-image-preview-wrapper').exists()).toBe(true) + + await wrapper.find('.ix-image-preview-wrapper').trigger('click') + + expect(wrapper.findComponent(ImageViewer).props('visible')).toBe(true) + + await wrapper.setProps({ preview: false }) + + expect(wrapper.find('.ix-image-preview-wrapper').exists()).toBe(false) }) - test('render objectFit work', async () => { - const wrapper = ImageMount({ - props: { - src: 'https://cdn.jsdelivr.net/gh/danranvm/image-hosting/images/idux.jpg', - }, - }) - expect(wrapper.html()).toMatchSnapshot() - await wrapper.setProps({ objectFit: 'fill' }) - expect(wrapper.find('img').attributes()['style']).toMatch('fill') + test('img attrs work', async () => { + const wrapper = ImageMount({ attrs: { alt: 'testAlt', width: '200' } }) + await flushPromises() + + expect(wrapper.find('.ix-image-inner').attributes('alt')).toBe('testAlt') + expect(wrapper.find('.ix-image-inner').attributes('width')).toBe('200') }) - test('render preview work', async () => { - const wrapper = ImageMount({ - props: { - preview: true, - width: 200, - height: 200, - src: 'https://cdn.jsdelivr.net/gh/danranvm/image-hosting/images/idux.jpg', - }, - }) + test('hooks work', async () => { + const onLoad = jest.fn() + const onError = jest.fn() + const wrapper = ImageMount({ props: { onLoad, onError } }) + await flushPromises() + await wrapper.find('.ix-image-inner').trigger('load') - expect(wrapper.html()).toMatchSnapshot() - await wrapper.find('.ix-image-preview-is').trigger('click') - - expect(wrapper.find('.ix-image-preview').exists()).toBe(true) - await wrapper.find('.ix-rotate-left').trigger('click') - expect(wrapper.find('.ix-image-preview-img>img').attributes()['style']).toEqual( - 'transform: scale3d(1, 1, 1) rotate(-90deg);', - ) - await wrapper.find('.ix-rotate-right').trigger('click') - - expect(wrapper.find('.ix-image-preview-img>img').attributes()['style']).toEqual( - 'transform: scale3d(1, 1, 1) rotate(0deg);', - ) - await wrapper.find('.ix-zoom-in').trigger('click') - expect(wrapper.find('.ix-image-preview-img>img').attributes()['style']).toEqual( - 'transform: scale3d(1.1, 1.1, 1) rotate(0deg);', - ) - await wrapper.find('.ix-zoom-out').trigger('click') - expect(wrapper.find('.ix-image-preview-img>img').attributes()['style']).toEqual( - 'transform: scale3d(1, 1, 1) rotate(0deg);', - ) - - let i = 10 - while (i) { - await wrapper.find('.ix-zoom-out').trigger('click') - i-- + expect(onLoad).toBeCalled() + + await wrapper.find('.ix-image-inner').trigger('error') + + expect(onError).toBeCalled() + }) + + test('imageViewerProps work', async () => { + const imageViewer = { + visible: true, + activeIndex: 0, + images: ['/a'], + zoom: [1, 2], + loop: false, + maskClosable: false, + target: 'ix-image-container', + 'onUpdate:visible': () => {}, + 'onUpdate:activeIndex': () => {}, } - expect(wrapper.find('.ix-zoom-out').attributes()['class']).toEqual( - 'ix-tools-item ix-zoom-out ix-tools-item-disabled', - ) + const wrapper = ImageMount({ props: { imageViewer } }) + await flushPromises() - await wrapper.find('.ix-close').trigger('click') - expect(wrapper.find('ix-image-preview').exists()).toBe(false) + expect(wrapper.findComponent(ImageViewer).props()).toEqual(imageViewer) }) - test('render fallback work', async () => { - const fallback = - 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTI4cHgiIGhlaWdodD0iMTI4cHgiIHZpZXdCb3g9IjAgMCAxMjggMTI4IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgogICAgPCEtLSBHZW5lcmF0b3I6IFNrZXRjaCA1OCAoODQ2NjMpIC0gaHR0cHM6Ly9za2V0Y2guY29tIC0tPgogICAgPHRpdGxlPjEyODwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxkZWZzPgogICAgICAgIDxsaW5lYXJHcmFkaWVudCB4MT0iNTAlIiB5MT0iMCUiIHgyPSI1MCUiIHkyPSI5OS42Mjc4NTA1JSIgaWQ9ImxpbmVhckdyYWRpZW50LTEiPgogICAgICAgICAgICA8c3RvcCBzdG9wLWNvbG9yPSIjMDBBQ0ZGIiBvZmZzZXQ9IjAlIj48L3N0b3A+CiAgICAgICAgICAgIDxzdG9wIHN0b3AtY29sb3I9IiMzMzY2RkYiIG9mZnNldD0iMTAwJSI+PC9zdG9wPgogICAgICAgIDwvbGluZWFyR3JhZGllbnQ+CiAgICAgICAgPGxpbmVhckdyYWRpZW50IHgxPSI1MCUiIHkxPSIwJSIgeDI9IjUwJSIgeTI9IjEwMCUiIGlkPSJsaW5lYXJHcmFkaWVudC0yIj4KICAgICAgICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iIzFEQjgzRiIgb2Zmc2V0PSIwJSI+PC9zdG9wPgogICAgICAgICAgICA8c3RvcCBzdG9wLWNvbG9yPSIjNzJEMTNEIiBvZmZzZXQ9IjEwMCUiPjwvc3RvcD4KICAgICAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPC9kZWZzPgogICAgPGcgaWQ9IjEyOCIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPHBhdGggZD0iTTQ0LjQ2NzMxMjYsNjIuMjMyMTcwNSBMNzAuMTI5ODAxNSw4My43NjU1NTU0IEM2MC4xODk3NTEyLDk1LjYxMTY0NiA0Mi41Mjg1OTQ2LDk3LjE1Njc5NyAzMC42ODI1MDQsODcuMjE2NzQ2NyBMMjguMjQ4MTQ5Miw4NS4xNzM4OTc1IEMyMS4zMTIxMTA4LDc5LjMwODkyOTQgMTQuMzM1NDQzOSw3My40NTQ4NzE4IDcuMzE4MTQ4NDYsNjcuNjExNzI0NiBMOS4yNDY1MTEyOSw2NS4zMTM1OTEzIEMxMy44NDgzODY0LDU5LjgyOTI5MDEgMjAuMzAzNzUwNyw1Ni44MTc3Nzk2IDI2LjkyNzcxNzYsNTYuNDI2NDY4MyBMMjguMzQ5MTA1Nyw1Ni4zODI2OTk1IEMzNC4wNDAwNjg2LDU2LjM2ODg1MDQgMzkuNzY2NDgzLDU4LjI4NzcwNjEgNDQuNDY3MzEyNiw2Mi4yMzIxNzA1IFoiIGlkPSLot6/lvoQiIGZpbGw9IiMyMDRFRDkiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNOTUuNDM4MDk4NCw0NS4xMzQ1MDkgTDk3LjMzMTY2NTYsNDUuMjI3OTkzMiBMOTguNzAxMDU1OSw0NS4zNjI3MDcyIEwxMDAuNTE2MTI5LDQ1LjYyOTI5MTYgTDEwMS44OTEyODcsNDUuODk5NDE2NCBMMTAzLjQzNjU4NSw0Ni4yNzUwNzE5IEwxMDUuMTY5NDEsNDYuNzkwNDA1MSBMMTA2LjU4NzY2NSw0Ny4yODk1OTM3IEwxMDguMjEyNTY1LDQ3Ljk1MjAwNzQgTDEwOC45MzE3Niw0OC4yNzc2NDAxIEwxMDguOTMxNzYsNDguMjc3NjQwMSBMMTEwLjM1Mzg5OCw0OC45ODM2MzEgTDExMS44MDUxMjgsNDkuNzk0NjA0NSBMMTEzLjQ0MzE5OSw1MC44MzAxOTgxIEwxMTQuNjUzNDk2LDUxLjY4NTY5MTIgTDExNS44MjE1MjEsNTIuNTkyMTI3NSBMMTE3LjE4ODUzNyw1My43NjU3NjYgTDExOC40NzMyOTksNTQuOTk1MDg2NCBMMTE5LjUzMzEzLDU2LjExNTU4MDYgTDEyMC4wODg2Miw1Ni43NDY0NjgzIEwxMjAuMDg4NjIsNTYuNzQ2NDY4MyBMMTIxLjIwODAwMyw1OC4xMjIyMDA1IEwxMjIuMTMwNDY5LDU5LjM3NzQ1NiBMNzIuMjk3NDUzMyw5NC4yNzA5MDkyIEM2Ny41NzI1ODc4LDk3LjU3OTI5NTcgNjIuMTMzNTU1NCw5OS4zMTAyNzggNTYuNjY4NDkzMSw5OS40OTg0MDA0IEw1NS4wMjg4ODE3LDk5LjUwODU4NTggQzQ4LjQ3MjgxNzUsOTkuMzY0NDYwNyA0MS45NzgzNjIyLDk3LjAwMzU5MTYgMzYuNzM0NjE2NSw5Mi40ODU2NzA5IEwzMi43NjgwODE2LDg5LjAzMzg2MiBMMzIuNzY4MDgxNiw4OS4wMzM4NjIgTDI3LjYzNjUwMzksODQuNjQ2ODk0IEw3NS42Mzg0ODQ5LDUxLjE0MzQ2MzQgQzc3LjQ1OTY4NTEsNDkuODcyMzM5NyA3OS4zNjM0NjM3LDQ4LjgwNzg1NjQgODEuMzIxOTc2Miw0Ny45NDUwODIgTDgzLjI5NzM3NzksNDcuMTQ5NDc5OSBDODQuMzIzMzMxMSw0Ni43NzQwNzgyIDg1LjM2MTE5MzEsNDYuNDUxNzgxNSA4Ni40MDcyNTc2LDQ2LjE4MTg4MTUgTDg3Ljk4MjA0MzEsNDUuODE2MjQwMyBMODkuNjQ0MTg2Niw0NS41MTY1NyBMOTEuMzg1OTMwMyw0NS4yOTQ4NTUxIEw5My4wNTA0MjI4LDQ1LjE2OTM0MjMgQzkzLjg0NjY4MzIsNDUuMTI5Mjc1NyA5NC42NDMxMDY0LDQ1LjExNzc1ODggOTUuNDM4MDk4NCw0NS4xMzQ1MDkgWiIgaWQ9Iui3r+W+hCIgZmlsbD0idXJsKCNsaW5lYXJHcmFkaWVudC0xKSI+PC9wYXRoPgogICAgICAgIDxwYXRoIGQ9Ik01MS4zODg4NTIsMzIuNDY5NDc1OSBMNjMuNjQ1NTYzMSw0Mi43NTQwNzc2IEM1OC42NzU1MzgsNDguNjc3MTIyOSA0OS44NDQ5NTk3LDQ5LjQ0OTY5ODQgNDMuOTIxOTE0NCw0NC40Nzk2NzMzIEwzMi44MTQyNywzNS4xNTkyNTMgTDMzLjc3ODQ1MTQsMzQuMDEwMTg2MyBDMzguMjE1OTczOCwyOC43MjE3NTMgNDYuMTAwNDE4NywyOC4wMzE5NTM0IDUxLjM4ODg1MiwzMi40Njk0NzU5IFoiIGlkPSLot6/lvoQiIGZpbGw9IiMwMzc4MkEiPjwvcGF0aD4KICAgICAgICA8cGF0aCBkPSJNNzIuMDY0NTA2MiwyNi4xMjg0NDA2IEw3My4yNjYzMDc5LDI2LjIyMjA0MTMgTDc0LjM3NzU0MTYsMjYuMzk3MDkgTDc1LjU3NjI3OTIsMjYuNjg0OTIyNCBMNzYuNDc4NTYyMywyNi45NzMxMDM2IEw3Ny41MTY0NTg5LDI3LjM4NjMzNDcgTDc3LjUxNjQ1ODksMjcuMzg2MzM0NyBMNzguMjAwNjU1MywyNy43MTA0NDczIEw3OS4zMDc1MTExLDI4LjMzMTQwODYgTDc5LjcwNDIsMjguNTg1OTgwNSBMODAuMjkxNTU3OCwyOC45OTc0NTY2IEw4MC44MDkzMjg3LDI5LjM5NzU4MjggTDgxLjIxODA3NjgsMjkuNzQwODE0MyBMODEuNzcyNzI0MywzMC4yNDk2ODA3IEw4Mi4yMTE2Mzk2LDMwLjY5MTk2MzIgTDgyLjIxMTYzOTYsMzAuNjkxOTYzMiBMODIuNjM0NzgxOCwzMS4xNTYxOTk0IEw4My4wOTIyMjcyLDMxLjcwNjIzMjEgTDgzLjYxOTAyNjMsMzIuNDEzNDQ1NSBMNjMuMTYzMzEwOCw0Ni43MzY2OTE3IEM2MC45ODg3ODEsNDguMjU5MzEzOSA1OC41MTE3ODE3LDQ5LjExMzY3NjggNTYuMDAwNjA2Myw0OS4zMTMzMzE2IEw1NC43NDM0MTI4LDQ5LjM1ODY2MjggQzUxLjM4OTkyNjQsNDkuMzM0Mzg5NyA0OC4wNTUxNzcxLDQ4LjE1MDIwNjEgNDUuMzc1MTE5NSw0NS44MzgyMzM0IEw0Mi4zNzc0OTk1LDQzLjI1MjMxMyBMNjMuMDcwNjgxNiwyOC43ODMyMDI2IEM2My42NzAxMjExLDI4LjM2NDA2MTggNjQuMjg5ODcxMywyNy45OTU2NDMzIDY0LjkyNDgzNTYsMjcuNjc3MDQ2OCBMNjUuOTUxNDUxMywyNy4yMTAyOTkxIEw2NS45NTE0NTEzLDI3LjIxMDI5OTEgTDY2LjU3MDYwOTYsMjYuOTczMjE1NyBMNjcuNzY3NzYyNywyNi42MDI1NDE2IEw2Ny43Njc3NjI3LDI2LjYwMjU0MTYgTDY4Ljk4MjU5NjQsMjYuMzM3MjA5MiBMNjkuNzk2ODcyMSwyNi4yMTg1ODE3IEw3MC4yMTY0MDg1LDI2LjE3NTQ3ODUgQzcwLjgzMjMzODgsMjYuMTIxMDkyMyA3MS40NDk2MzQ3LDI2LjEwNTYzNjEgNzIuMDY0NTA2MiwyNi4xMjg0NDA2IFoiIGlkPSLot6/lvoQiIGZpbGw9InVybCgjbGluZWFyR3JhZGllbnQtMikiPjwvcGF0aD4KICAgIDwvZz4KPC9zdmc+' + + test('slots work', async () => { const wrapper = ImageMount({ - props: { - fallback, - src: '', + slots: { + previewIcon: h('div', { class: 'slot-previewIcon' }), + placeholder: h('div', { class: 'slot-placeholder' }), + fallback: h('div', { class: 'slot-fallback' }), }, }) - await wrapper.find('img').trigger('error') + await flushPromises() + + expect(wrapper.find('.slot-placeholder').exists()).toBe(true) + + await wrapper.find('.ix-image-inner').trigger('load') + + expect(wrapper.find('.slot-previewIcon').exists()).toBe(true) + + await wrapper.find('.ix-image-inner').trigger('error') - expect(wrapper.find('.ix-image-error').isVisible()).toBe(true) + expect(wrapper.find('.slot-fallback').exists()).toBe(true) }) }) diff --git a/packages/components/image/__tests__/imageViewer.spec.ts b/packages/components/image/__tests__/imageViewer.spec.ts new file mode 100644 index 000000000..bb65ec3c7 --- /dev/null +++ b/packages/components/image/__tests__/imageViewer.spec.ts @@ -0,0 +1,209 @@ +import type { ImageViewerProps } from '../src/types' +import type { MountingOptions } from '@vue/test-utils' + +import { flushPromises, mount } from '@vue/test-utils' + +import { renderWork, wait } from '@tests' + +import ImageViewer from '../src/ImageViewer' +import ImageViewerContent from '../src/component/ImageViewerContent' + +describe('ImageViewer', () => { + beforeEach(() => { + const el = document.createElement('div') + el.className = 'ix-image-viewer-container' + document.body.appendChild(el) + }) + + afterEach(() => { + ;(document.querySelector('.ix-image-viewer-container') as HTMLElement).innerHTML = '' + }) + + const ImageViewerMount = (options: MountingOptions> = {}) => { + const { props = {}, ...rest } = options + const images = ['/icons/logo.svg'] + const activeIndex = 0 + return mount(ImageViewer, { + ...({ + ...rest, + props: { images, activeIndex, ...props }, + } as MountingOptions), + }) + } + + renderWork(ImageViewer, { props: { images: ['/icons/logo.svg'] } }) + + test('v-model:visible work', async () => { + const onUpdateVisible = jest.fn() + const wrapper = ImageViewerMount({ props: { visible: false, 'onUpdate:visible': onUpdateVisible } }) + await flushPromises() + + expect(wrapper.findComponent(ImageViewerContent).exists()).toBe(false) + + await wrapper.setProps({ visible: true }) + + expect(wrapper.findComponent(ImageViewerContent).exists()).toBe(true) + + await wrapper.findComponent(ImageViewerContent).find('img').trigger('click') + + expect(onUpdateVisible).toBeCalledWith(false) + + await wrapper.findComponent(ImageViewerContent).find('.ix-icon-close').trigger('click') + + expect(onUpdateVisible).toBeCalledWith(false) + + await wrapper.findComponent(ImageViewerContent).trigger('keydown.esc') + + expect(onUpdateVisible).toBeCalledWith(false) + }) + + test('v-model:activeIndex work', async () => { + const onUpdateActiveIndex = jest.fn() + const images = ['/1.png', '/2.png', '/3.png'] + const wrapper = ImageViewerMount({ + props: { images, visible: true, activeIndex: 0, 'onUpdate:activeIndex': onUpdateActiveIndex }, + }) + await flushPromises() + + expect(wrapper.findComponent(ImageViewerContent).find('img').attributes('src')).toBe(images[0]) + + await wrapper.setProps({ activeIndex: 1 }) + + expect(wrapper.findComponent(ImageViewerContent).find('img').attributes('src')).toBe(images[1]) + + await wrapper.findComponent(ImageViewerContent).trigger('keydown', { code: 'ArrowRight' }) + await wait(10) // debounce + + expect(onUpdateActiveIndex).toBeCalledWith(2) + + await wrapper.findComponent(ImageViewerContent).trigger('keydown', { code: 'ArrowLeft' }) + await wait(10) // debounce + + expect(onUpdateActiveIndex).toBeCalledWith(0) + + await wrapper.findComponent(ImageViewerContent).find('.ix-icon-left').trigger('click') + await wait(10) // debounce + + expect(onUpdateActiveIndex).toBeCalledWith(0) + + await wrapper.findComponent(ImageViewerContent).find('.ix-icon-right').trigger('click') + await wait(10) // debounce + + expect(onUpdateActiveIndex).toBeCalledWith(2) + }) + + test('images work', async () => { + const imagesOld = ['/1.png'] + const wrapper = ImageViewerMount({ + props: { images: imagesOld, visible: true, activeIndex: 0 }, + }) + await flushPromises() + + expect(wrapper.findComponent(ImageViewerContent).find('img').attributes('src')).toBe(imagesOld[0]) + + const imageNew = ['/2.png'] + await wrapper.setProps({ images: imageNew }) + + expect(wrapper.findComponent(ImageViewerContent).find('img').attributes('src')).toBe(imageNew[0]) + }) + + test('zoom work', async () => { + const wrapper = ImageViewerMount({ props: { visible: true } }) + await flushPromises() + + await wrapper.findComponent(ImageViewerContent).trigger('mousewheel', { wheelDelta: 10 }) + await wait(10) // debounce + + expect(wrapper.findComponent(ImageViewerContent).find('img').attributes('style')).toMatch('scale(1.2)') + + await wrapper.findComponent(ImageViewerContent).trigger('mousewheel', { wheelDelta: -10 }) + await wait(10) // debounce + + expect(wrapper.findComponent(ImageViewerContent).find('img').attributes('style')).toMatch('scale(1)') + + await wrapper.findComponent(ImageViewerContent).find('.ix-icon-zoom-in').trigger('click') + await wait(10) // debounce + + expect(wrapper.findComponent(ImageViewerContent).find('img').attributes('style')).toMatch('scale(1.2)') + + await wrapper.findComponent(ImageViewerContent).find('.ix-icon-zoom-out').trigger('click') + await wait(10) // debounce + + expect(wrapper.findComponent(ImageViewerContent).find('img').attributes('style')).toMatch('scale(1)') + + await wrapper.setProps({ zoom: [0.8, 0.9] }) + + expect(wrapper.findComponent(ImageViewerContent).find('img').attributes('style')).toMatch('scale(0.9)') + + await wrapper.findComponent(ImageViewerContent).find('.ix-icon-zoom-in').trigger('click') + await wait(10) // debounce + + expect(wrapper.findComponent(ImageViewerContent).find('img').attributes('style')).toMatch('scale(0.9)') + + await wrapper.setProps({ zoom: [1.1, 1.2] }) + + expect(wrapper.findComponent(ImageViewerContent).find('img').attributes('style')).toMatch('scale(1.1)') + + await wrapper.findComponent(ImageViewerContent).find('.ix-icon-zoom-out').trigger('click') + await wait(10) // debounce + + expect(wrapper.findComponent(ImageViewerContent).find('img').attributes('style')).toMatch('scale(1.1)') + }) + + test('loop work', async () => { + const images = ['/1.png', '/2.png', '/3.png'] + const onUpdateActiveIndex = jest.fn() + const wrapper = ImageViewerMount({ + props: { images, visible: true, activeIndex: 0, 'onUpdate:activeIndex': onUpdateActiveIndex }, + }) + await flushPromises() + + await wrapper.findComponent(ImageViewerContent).find('.ix-icon-left').trigger('click') + await wait(10) // debounce + + expect(onUpdateActiveIndex).toBeCalledWith(2) + + await wrapper.setProps({ activeIndex: 2 }) + await wrapper.findComponent(ImageViewerContent).find('.ix-icon-right').trigger('click') + await wait(10) // debounce + + expect(onUpdateActiveIndex).toBeCalledWith(0) + + await wrapper.setProps({ loop: false }) + + expect(wrapper.findComponent(ImageViewerContent).find('.ix-icon-right').classes().toString()).toMatch('disabled') + + await wrapper.setProps({ activeIndex: 0 }) + + expect(wrapper.findComponent(ImageViewerContent).find('.ix-icon-left').classes().toString()).toMatch('disabled') + }) + + test('maskClosable work', async () => { + const onUpdateVisible = jest.fn() + const wrapper = ImageViewerMount({ + props: { visible: true, 'onUpdate:visible': onUpdateVisible }, + }) + await wrapper.findComponent(ImageViewerContent).find('img').trigger('click') + + expect(onUpdateVisible).toBeCalledWith(false) + onUpdateVisible.mockRestore() + + await wrapper.setProps({ maskClosable: false }) + await wrapper.findComponent(ImageViewerContent).find('.ix-image-viewer-preview').trigger('click') + + expect(onUpdateVisible).not.toBeCalled() + }) + + test('target work', async () => { + const wrapper = ImageViewerMount({ props: { visible: true } }) + await flushPromises() + + expect((document.querySelector('.ix-image-viewer-container .ix-image-viewer') as HTMLElement).innerHTML).not.toBe( + '', + ) + + await wrapper.setProps({ target: 'image-viewer-container' }) + + expect((document.querySelector('.image-viewer-container .ix-image-viewer') as HTMLElement).innerHTML).not.toBe('') + }) +}) diff --git a/packages/components/image/demo/Basic.md b/packages/components/image/demo/Basic.md index 601253ba3..6887d94f7 100644 --- a/packages/components/image/demo/Basic.md +++ b/packages/components/image/demo/Basic.md @@ -2,9 +2,13 @@ order: 0 title: zh: 基本使用 - + en: Basic usage --- ## zh -可通过`fit`确定图片如何适应到容器框,同原生 [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-objectFit) +最简单的用法,`src`是必须的。 + +## en + +The simplest usage, `src` is required. diff --git a/packages/components/image/demo/Basic.vue b/packages/components/image/demo/Basic.vue index 5dda5ba2d..b50c999f0 100644 --- a/packages/components/image/demo/Basic.vue +++ b/packages/components/image/demo/Basic.vue @@ -1,31 +1,3 @@ - - - - diff --git a/packages/components/image/demo/Fallback.md b/packages/components/image/demo/Fallback.md deleted file mode 100644 index 30ef1675c..000000000 --- a/packages/components/image/demo/Fallback.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -order: 0 -title: - zh: 容错处理 - ---- -## zh - -通过设置`fallback`,当图片加载失败的时候会显示此占位内容 diff --git a/packages/components/image/demo/Fallback.vue b/packages/components/image/demo/Fallback.vue deleted file mode 100644 index df0a7fff1..000000000 --- a/packages/components/image/demo/Fallback.vue +++ /dev/null @@ -1,16 +0,0 @@ - - diff --git a/packages/components/image/demo/ImageAttrs.md b/packages/components/image/demo/ImageAttrs.md new file mode 100644 index 000000000..d7b822b76 --- /dev/null +++ b/packages/components/image/demo/ImageAttrs.md @@ -0,0 +1,14 @@ +--- +order: 1 +title: + zh: img标签属性 + en: Img tag attributes +--- + +## zh + +继承所有 [img标签属性](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)。 + +## en + +Inherit all [img tag attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img). diff --git a/packages/components/image/demo/ImageAttrs.vue b/packages/components/image/demo/ImageAttrs.vue new file mode 100644 index 000000000..25ab074d6 --- /dev/null +++ b/packages/components/image/demo/ImageAttrs.vue @@ -0,0 +1,14 @@ + + + diff --git a/packages/components/image/demo/ImageSlot.md b/packages/components/image/demo/ImageSlot.md new file mode 100644 index 000000000..a759f3207 --- /dev/null +++ b/packages/components/image/demo/ImageSlot.md @@ -0,0 +1,14 @@ +--- +order: 3 +title: + zh: 自定义插槽展示 + en: Slots +--- + +## zh + +支持自定义预览图标 `previewIcon`、加载中展示 `placeholder`、加载失败展示 `fallback`。 + +## en + +Support custom preview icon `previewIcon`, loading display `placeholder`, loading failure display `fallback`. diff --git a/packages/components/image/demo/ImageSlot.vue b/packages/components/image/demo/ImageSlot.vue new file mode 100644 index 000000000..cb4532603 --- /dev/null +++ b/packages/components/image/demo/ImageSlot.vue @@ -0,0 +1,23 @@ + + + diff --git a/packages/components/image/demo/ImageViewer.md b/packages/components/image/demo/ImageViewer.md new file mode 100644 index 000000000..4785e64bd --- /dev/null +++ b/packages/components/image/demo/ImageViewer.md @@ -0,0 +1,14 @@ +--- +order: 4 +title: + zh: 预览组件 + en: ImageViewer +--- + +## zh + +支持单独使用预览组件`IxImageViewer`,`images`是必须的。 + +## en + +Supports separate use of the preview component `IxImageViewer`, `images` is a must. diff --git a/packages/components/image/demo/ImageViewer.vue b/packages/components/image/demo/ImageViewer.vue new file mode 100644 index 000000000..bbe577a1f --- /dev/null +++ b/packages/components/image/demo/ImageViewer.vue @@ -0,0 +1,18 @@ + + + diff --git a/packages/components/image/demo/Preview.md b/packages/components/image/demo/Preview.md index b9463a20b..c2baf7ba8 100644 --- a/packages/components/image/demo/Preview.md +++ b/packages/components/image/demo/Preview.md @@ -1,10 +1,14 @@ --- -order: 0 +order: 2 title: - zh: 预览大图 - + zh: 图片预览 + en: Preview --- ## zh -通过设置`preview`为`true`,使成功加载的图片有点击预览大图的功能 +已内置预览组件`IxImageViewer`,使用 `preview` 控制是否开启图片预览,默认开启,可通过 `imageViewer` 配置预览组件,预览组件配置同 [imageViewerProps](/components/image/zh#ImageViewerProps) 。 + +## en + +Has built-in preview component `IxImageViewer`, use `preview` to control whether to open the image preview, it is enabled by default, and the preview component can be configured through `imageViewer`. The preview component props is the same as [imageViewerProps](/components/image/zh#ImageViewerProps). diff --git a/packages/components/image/demo/Preview.vue b/packages/components/image/demo/Preview.vue index d73d93ff6..204900728 100644 --- a/packages/components/image/demo/Preview.vue +++ b/packages/components/image/demo/Preview.vue @@ -1,3 +1,3 @@ diff --git a/packages/components/image/demo/Zoom.md b/packages/components/image/demo/Zoom.md new file mode 100644 index 000000000..71e8d345e --- /dev/null +++ b/packages/components/image/demo/Zoom.md @@ -0,0 +1,14 @@ +--- +order: 5 +title: + zh: 缩放 + en: Zoom +--- + +## zh + +支持预览图的缩放范围,使用 `zoom` ,默认为 `[0.5, 2]`,表示最小展示0.5倍尺寸,最大展示2倍。 + +## en + +Support the zoom range of the preview image, use `zoom`, the default is `[0.5, 2]`, which means that the minimum display size is 0.5 times, and the maximum display size is 2 times. diff --git a/packages/components/image/demo/Zoom.vue b/packages/components/image/demo/Zoom.vue new file mode 100644 index 000000000..c674456b6 --- /dev/null +++ b/packages/components/image/demo/Zoom.vue @@ -0,0 +1,20 @@ + + + diff --git a/packages/components/image/docs/Design.en.md b/packages/components/image/docs/Design.en.md new file mode 100644 index 000000000..d1e713d5e --- /dev/null +++ b/packages/components/image/docs/Design.en.md @@ -0,0 +1,3 @@ +## Description + +## Usage scenarios diff --git a/packages/components/image/docs/Design.zh.md b/packages/components/image/docs/Design.zh.md index 0aab31037..933801767 100644 --- a/packages/components/image/docs/Design.zh.md +++ b/packages/components/image/docs/Design.zh.md @@ -1,4 +1,3 @@ -## 何时使用 +## 组件定义 -- 需要展示图片时使用。 -- 加载大图时显示或加载失败时容错处理。 +## 使用场景 diff --git a/packages/components/image/docs/Index.en.md b/packages/components/image/docs/Index.en.md new file mode 100644 index 000000000..5c62d0349 --- /dev/null +++ b/packages/components/image/docs/Index.en.md @@ -0,0 +1,29 @@ +--- +category: components +type: Data Display +order: 0 +title: Image +subtitle: +--- + +## API + +### IxImage + +#### ImageProps + +| Name | Description | Type | Default | Global Config | Remark | +| --- | --- | --- | --- | --- | --- | +| - | - | - | - | ✅ | - | + +#### ImageSlots + +| Name | Description | Parameter Type | Remark | +| --- | --- | --- | --- | +| - | - | - | - | + +#### ImageMethods + +| Name | Description | Parameter Type | Remark | +| --- | --- | --- | --- | +| - | - | - | - | diff --git a/packages/components/image/docs/Index.zh.md b/packages/components/image/docs/Index.zh.md index b6cbe4c91..dcd0762f4 100644 --- a/packages/components/image/docs/Index.zh.md +++ b/packages/components/image/docs/Index.zh.md @@ -1,6 +1,7 @@ --- category: components type: 数据展示 +order: 0 title: Image subtitle: 图片 --- @@ -9,20 +10,38 @@ subtitle: 图片 ### IxImage +图片组件 + #### ImageProps +> 继承所有 [img标签属性](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) + | 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 | | --- | --- | --- | --- | --- | --- | | `src` | 图片地址 | `string` | - | - | - | -| `width` | 图像宽度 | `string \| number` | - | ✅ | - | -| `height` | 图像高度 | `string \| number` | - | ✅ | - | -| `fallback` | 加载失败容错地址 | `string` | - | ✅ | - | -| `preview` | 预览参数,为 `false` 时禁用 | `boolean` | - | - | - | -| `alt` | 图像描述 | `string` | - | - | - | -| `objectFit` | 确定图片如何适应容器框 | `string` | - | - | 同原生 [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) | +| `preview` | 是否开启预览 | `boolean` | `true` | ✅ | - | +| `imageViewer` | 预览组件`IxImageViewer`的配置 | `ImageViewerProps` | `{}` | - | - | -#### ImageEmits +#### ImageSlots | 名称 | 说明 | 参数类型 | 备注 | | --- | --- | --- | --- | -| `statusChange` | 图片加载状态改变时触发 | `loading\|loaded\|failed` | - | +| `previewIcon` | 预览的icon | - | - | +| `placeholder` | 图片未加载的占位内容 | - | - | +| `fallback` | 加载失败时展示内容 | - | - | + +### IxImageViewer + +图片预览组件 + +#### ImageViewerProps + +| 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 | +| --- | --- | --- | --- | --- | --- | +| `v-model:visible` | 是否可见 | `boolean` | `false` | - | - | +| `v-model:activeIndex` | 当前激活的索引 | `number` | `0` | - | - | +| `images` | 用于预览的图片链接列表,必选 | `string[]` | `[]` | - | - | +| `loop` | 是否无限循环 | `boolean` | `true` | ✅ | - | +| `zoom` | 可缩放的倍数范围 | `number[]` | `[0.5, 2]` | ✅ | - | +| `target` | 预览窗口容器节点 | `string \| HTMLElement \| () => string \| HTMLElement` | - | ✅ | - | +| `maskClosable` | 是否可以通过点击遮罩层关闭预览 | `boolean` | `true` | ✅ | - | diff --git a/packages/components/image/index.ts b/packages/components/image/index.ts index d7a2d2619..fdf949a3b 100644 --- a/packages/components/image/index.ts +++ b/packages/components/image/index.ts @@ -5,12 +5,22 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import type { ImageComponent } from './src/types' +import type { ImageComponent, ImageViewerComponent } from './src/types' -import Image from './src/Image.vue' +import Image from './src/Image' +import ImageViewer from './src/ImageViewer' const IxImage = Image as unknown as ImageComponent +const IxImageViewer = ImageViewer as unknown as ImageViewerComponent -export { IxImage } +export { IxImage, IxImageViewer } -export type { ImageInstance, ImageComponent, ImagePublicProps as ImageProps, ImageStatus } from './src/types' +export type { + ImageStatus, + ImageInstance, + ImageComponent, + ImagePublicProps as ImageProps, + ImageViewerInstance, + ImageViewerComponent, + ImageViewerPublicProps as ImageViewerProps, +} from './src/types' diff --git a/packages/components/image/src/Image.tsx b/packages/components/image/src/Image.tsx new file mode 100644 index 000000000..3481bd874 --- /dev/null +++ b/packages/components/image/src/Image.tsx @@ -0,0 +1,160 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { ImageProps, ImageStatus } from './types' +import type { ImageConfig } from '@idux/components/config' +import type { CSSProperties, ComputedRef, Ref, Slots } from 'vue' + +import { computed, defineComponent, normalizeClass, ref, watch } from 'vue' + +import { callEmit } from '@idux/cdk/utils' +import { useGlobalConfig } from '@idux/components/config' +import { IxIcon } from '@idux/components/icon' + +import ImageViewer from './ImageViewer' +import { imageProps } from './types' + +export default defineComponent({ + name: 'IxImage', + inheritAttrs: false, + props: imageProps, + setup(props, { attrs, slots }) { + const { class: className, style, ...rest } = attrs + const common = useGlobalConfig('common') + const mergedPrefixCls = computed(() => `${common.prefixCls}-image`) + const config = useGlobalConfig('image') + const preview = usePreview(props, config) + const { status, setFailed, setLoaded } = useStatus(props) + const [viewerVisible, setVisible] = useViewerVisible() + const { outerClasses, overLayerClasses, imageClasses } = useClasses( + mergedPrefixCls, + className as string, + status, + preview, + ) + + return () => { + const imageViewerProps = { + visible: viewerVisible.value, + 'onUpdate:visible': setVisible, + images: [props.src], + ...(props.imageViewer ?? {}), + } + + return ( +
+
+ {renderPreviewIcon(props, slots, mergedPrefixCls, status, preview, setVisible)} + {renderPlaceholder(slots, mergedPrefixCls, status)} + {renderFallback(slots, mergedPrefixCls, status)} + +
+ +
+ ) + } + }, +}) + +function renderPreviewIcon( + props: ImageProps, + slots: Slots, + mergedPrefixCls: ComputedRef, + status: Ref, + preview: ComputedRef, + setVisible: (visible: boolean) => void, +) { + return ( + status.value === 'loaded' && + preview.value && ( + setVisible(true)}> + {slots.previewIcon?.() ?? } + + ) + ) +} + +function renderPlaceholder(slots: Slots, mergedPrefixCls: ComputedRef, status: Ref) { + return ( + status.value === 'loading' && + (slots.placeholder?.() ?? ) + ) +} + +function renderFallback(slots: Slots, mergedPrefixCls: ComputedRef, status: Ref) { + return ( + status.value === 'failed' && + (slots.fallback?.() ?? ) + ) +} + +function useViewerVisible(): [Ref, (visible: boolean) => void] { + const viewerVisible = ref(false) + const setVisible = (visible: boolean) => { + viewerVisible.value = visible + } + return [viewerVisible, setVisible] +} + +function useClasses( + mergedPrefixCls: ComputedRef, + className: string, + status: Ref, + preview: ComputedRef, +) { + const outerClasses = computed(() => + normalizeClass([ + mergedPrefixCls.value, + className, + `${mergedPrefixCls.value}-${status.value}`, + { [`${mergedPrefixCls.value}-preview`]: preview.value }, + ]), + ) + const overLayerClasses = computed(() => normalizeClass(`${mergedPrefixCls.value}-layer`)) + const imageClasses = computed(() => + normalizeClass([ + `${mergedPrefixCls.value}-inner`, + { [`${mergedPrefixCls.value}-inner-hidden`]: status.value !== 'loaded' }, + ]), + ) + + return { + outerClasses, + overLayerClasses, + imageClasses, + } +} + +function usePreview(props: ImageProps, config: ImageConfig) { + return computed(() => props.preview ?? config.preview) +} + +function useStatus(props: ImageProps) { + const status: Ref = ref('loading') + const setLoaded = (e: Event) => { + status.value = 'loaded' + callEmit(props.onLoad, e) + } + const setFailed = (e: Event) => { + status.value = 'failed' + callEmit(props.onError, e) + } + + watch( + () => props.src, + () => { + status.value = 'loading' + }, + { immediate: true }, + ) + + return { + status, + setLoaded, + setFailed, + } +} diff --git a/packages/components/image/src/Image.vue b/packages/components/image/src/Image.vue deleted file mode 100644 index 281e2401d..000000000 --- a/packages/components/image/src/Image.vue +++ /dev/null @@ -1,93 +0,0 @@ - - diff --git a/packages/components/image/src/ImageViewer.tsx b/packages/components/image/src/ImageViewer.tsx new file mode 100644 index 000000000..5c5df5d36 --- /dev/null +++ b/packages/components/image/src/ImageViewer.tsx @@ -0,0 +1,43 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { ImageViewerProps } from './types' +import type { ImageViewerConfig } from '@idux/components/config' +import type { ComputedRef } from 'vue' + +import { Transition, computed, defineComponent } from 'vue' + +import { CdkPortal } from '@idux/cdk/portal' +import { useControlledProp } from '@idux/cdk/utils' +import { useGlobalConfig } from '@idux/components/config' + +import ImageViewerContent from './component/ImageViewerContent' +import { imageViewerProps } from './types' + +export default defineComponent({ + name: 'IxImageViewer', + props: imageViewerProps, + setup(props) { + const common = useGlobalConfig('common') + const config = useGlobalConfig('imageViewer') + const [visible] = useControlledProp(props, 'visible', false) + const mergedPrefixCls = computed(() => `${common.prefixCls}-image-viewer`) + const target = useTarget(props, config, mergedPrefixCls) + + return () => ( + + + {visible.value && } + + + ) + }, +}) + +function useTarget(props: ImageViewerProps, config: ImageViewerConfig, mergedPrefixCls: ComputedRef) { + return computed(() => props.target ?? config.target ?? `${mergedPrefixCls.value}-container`) +} diff --git a/packages/components/image/src/ImgPreview.vue b/packages/components/image/src/ImgPreview.vue deleted file mode 100644 index f7ddc3571..000000000 --- a/packages/components/image/src/ImgPreview.vue +++ /dev/null @@ -1,83 +0,0 @@ - - - diff --git a/packages/components/image/src/component/ImageViewerContent.tsx b/packages/components/image/src/component/ImageViewerContent.tsx new file mode 100644 index 000000000..fbce58fc2 --- /dev/null +++ b/packages/components/image/src/component/ImageViewerContent.tsx @@ -0,0 +1,286 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { ImageViewerContentProps } from '../types' +import type { ImageViewerConfig } from '@idux/components/config' +import type { ComputedRef, Ref } from 'vue' + +import { computed, defineComponent, normalizeClass, onBeforeUnmount, onMounted, ref, watch, watchEffect } from 'vue' + +import { debounce } from 'lodash-es' + +import { isFirefox } from '@idux/cdk/platform' +import { useControlledProp } from '@idux/cdk/utils' +import { useGlobalConfig } from '@idux/components/config' +import { IxIcon } from '@idux/components/icon' + +import { imageViewerContentProps } from '../types' + +const mousewheelEventName = isFirefox ? 'DOMMouseScroll' : 'mousewheel' +const debounceTime = 10 + +type ScaleType = 'in' | 'out' +type RotateType = 'left' | 'right' +type GoType = 'previous' | 'next' + +interface OprType { + icon: string + key: string + opr: () => void + visible: boolean + disabled?: boolean +} + +export default defineComponent({ + name: 'IxImageViewerContent', + props: imageViewerContentProps, + setup(props) { + const config = useGlobalConfig('imageViewer') + const zoom = useZoomRange(props, config) + const maskClosable = useMaskClosable(props, config) + const [visible, setVisible] = useControlledProp(props, 'visible', false) + const { calcTransform, scaleDisabled, rotateHandle, scaleHandle, resetTransform } = useStyleOpr(zoom) + const { activeIndex, switchDisabled, switchVisible, goHandle } = useSwitch(props, config) + const oprList = useOprList( + { + goNext: () => goHandle('next'), + goPrevious: () => goHandle('previous'), + rotateLeft: () => rotateHandle('left'), + rotateRight: () => rotateHandle('right'), + zoomOut: () => scaleHandle('out'), + zoomIn: () => scaleHandle('in'), + close: () => setVisible(false), + }, + scaleDisabled, + switchDisabled, + switchVisible, + ) + const { onWheelScroll, onKeydown } = getImageEvent(visible, { setVisible, scaleHandle, goHandle }) + const onClickLayer = () => maskClosable.value && setVisible(false) + + onMounted(() => { + window.addEventListener(mousewheelEventName, onWheelScroll, { passive: false, capture: false }) + window.addEventListener('keydown', onKeydown, false) + }) + + onBeforeUnmount(() => { + window.removeEventListener(mousewheelEventName, onWheelScroll) + window.removeEventListener('keydown', onKeydown) + }) + + watch([visible, activeIndex], ([visible$$]) => { + visible$$ && resetTransform() + }) + + return () => ( +
+ {renderOprNode(props, oprList)} + {renderPreviewImg(props, calcTransform, activeIndex, onClickLayer)} +
+ ) + }, +}) + +function renderOprNode(props: ImageViewerContentProps, oprList: ComputedRef) { + return ( +
+ {oprList.value + .filter(item => item.visible) + .map(item => { + const iconClasses = computed(() => + normalizeClass([ + `${props.mergedPrefixCls}-opr-item`, + { [`${props.mergedPrefixCls}-opr-item-disabled`]: item.disabled }, + ]), + ) + return + })} +
+ ) +} + +function renderPreviewImg( + props: ImageViewerContentProps, + calcTransform: ComputedRef>, + activeIndex: ComputedRef, + onClickLayer: () => void, +) { + const curImgSrc = (props.images ?? [])[activeIndex.value] + return ( +
+ +
+ ) +} + +function useOprList( + { goNext, goPrevious, rotateLeft, rotateRight, zoomOut, zoomIn, close }: Record void>, + scaleDisabled: ComputedRef>, + switchDisabled: ComputedRef>, + switchVisible: ComputedRef, +): ComputedRef { + return computed(() => [ + { + key: 'goPrevious', + icon: 'left', + opr: goPrevious, + disabled: switchDisabled.value.previous, + visible: switchVisible.value, + }, + { key: 'goNext', icon: 'right', opr: goNext, disabled: switchDisabled.value.next, visible: switchVisible.value }, + { key: 'rotateLeft', icon: 'rotate-left', opr: rotateLeft, visible: true }, + { key: 'rotateRight', icon: 'rotate-right', opr: rotateRight, visible: true }, + { key: 'zoomOut', icon: 'zoom-out', opr: zoomOut, disabled: scaleDisabled.value.out, visible: true }, + { key: 'zoomIn', icon: 'zoom-in', opr: zoomIn, disabled: scaleDisabled.value.in, visible: true }, + { key: 'close', icon: 'close', opr: close, visible: true }, + ]) +} + +function useSwitch(props: ImageViewerContentProps, config: ImageViewerConfig) { + const [activeIndex, setIndex] = useControlledProp(props, 'activeIndex', 0) + const loop = computed(() => props.loop ?? config.loop) + const switchDisabled = computed(() => ({ + previous: !loop.value && activeIndex.value === 0, + next: !loop.value && activeIndex.value === props.images.length - 1, + })) + const switchVisible = computed(() => props.images.length > 1) + const goHandle = debounce((direction: GoType = 'next') => { + if (direction === 'next') { + if (switchDisabled.value.next) { + return + } + setIndex(activeIndex.value >= props.images.length - 1 ? 0 : activeIndex.value + 1) + return + } + if (switchDisabled.value.previous) { + return + } + setIndex(activeIndex.value <= 0 ? props.images.length - 1 : activeIndex.value - 1) + }, debounceTime) + + return { + activeIndex, + switchDisabled, + switchVisible, + goHandle, + } +} + +function useStyleOpr(zoom: ComputedRef) { + const initScale = computed(() => getInitScale(zoom.value)) + const initRotate = 0 + const scale = ref(1) + const rotate = ref(initRotate) + const rotateFactor = { + left: -1, + right: 1, + } as const + const scaleFactor = { + in: 1, + out: -1, + } + + watchEffect(() => (scale.value = initScale.value)) + + const scaleDisabled = computed(() => ({ + in: scale.value >= zoom.value[1], + out: scale.value <= zoom.value[0], + })) + const calcTransform = computed(() => ({ transform: `scale(${scale.value}) rotate(${rotate.value}deg)` })) + + const rotateHandle = debounce((direction: RotateType = 'left', rotateStep = 90) => { + rotate.value = rotate.value + rotateStep * rotateFactor[direction] + }, debounceTime) + + const scaleHandle = debounce((direction: ScaleType, scaleStep = 0.2) => { + if (scaleDisabled.value[direction]) { + return + } + scale.value = scale.value + scaleStep * scaleFactor[direction] + }, debounceTime) + + const resetTransform = () => { + scale.value = initScale.value + rotate.value = initRotate + } + + return { + calcTransform, + scaleDisabled, + rotateHandle, + scaleHandle, + resetTransform, + } +} + +function useZoomRange(props: ImageViewerContentProps, config: ImageViewerConfig) { + return computed(() => props.zoom ?? config.zoom) +} + +function useMaskClosable(props: ImageViewerContentProps, config: ImageViewerConfig) { + return computed(() => props.maskClosable ?? config.maskClosable) +} + +function getImageEvent( + visible: Ref, + { + setVisible, + scaleHandle, + goHandle, + }: { + setVisible: (visible: boolean) => void + scaleHandle: (direction: ScaleType, step?: number) => void + goHandle: (direction: GoType) => void + }, +) { + const scroll = (e: WheelEvent | Event) => { + if (!visible.value) { + return + } + const event = e as WheelEvent & { wheelDelta?: number } + event.preventDefault() + const delta = event.wheelDelta ?? -event.detail + if (delta > 0) { + scaleHandle('in', 0.2) + } else { + scaleHandle('out', 0.2) + } + } + + const keyHandle: Record void> = { + ArrowUp: () => scaleHandle('in', 0.2), + ArrowDown: () => scaleHandle('out', 0.2), + ArrowLeft: () => goHandle('previous'), + ArrowRight: () => goHandle('next'), + Escape: () => setVisible(false), + } + const keyDown = (e: KeyboardEvent) => { + if (!visible.value) { + return + } + e.preventDefault() + if (e.code in keyHandle) { + keyHandle[e.code]() + } + } + + return { + onWheelScroll: scroll, + onKeydown: keyDown, + } +} + +function getInitScale(zoom: number[]) { + const defaultScale = 1 + if (zoom[0] > defaultScale) { + return zoom[0] + } + if (zoom[1] < defaultScale) { + return zoom[1] + } + return defaultScale +} diff --git a/packages/components/image/src/types.ts b/packages/components/image/src/types.ts index 64a7c03bf..fbcec0981 100644 --- a/packages/components/image/src/types.ts +++ b/packages/components/image/src/types.ts @@ -8,18 +8,34 @@ import type { IxInnerPropTypes, IxPublicPropTypes } from '@idux/cdk/utils' import type { DefineComponent, HTMLAttributes } from 'vue' +import { ɵPortalTargetDef } from '@idux/cdk/portal' import { IxPropTypes } from '@idux/cdk/utils' export type ImageStatus = 'loading' | 'loaded' | 'failed' +const zoomValidator = { + validator: (val: number[]) => val.length === 2, + msg: 'zoom only accepts the length of the array is 2', +} + +export const imageViewerProps = { + visible: IxPropTypes.bool, + activeIndex: IxPropTypes.number, + images: IxPropTypes.array().isRequired, + zoom: IxPropTypes.custom(zoomValidator.validator, zoomValidator.msg), + loop: IxPropTypes.bool, + target: ɵPortalTargetDef, + maskClosable: IxPropTypes.bool, + 'onUpdate:visible': IxPropTypes.emit<(visible: boolean) => void>(), + 'onUpdate:activeIndex': IxPropTypes.emit<(curIndex: number) => void>(), +} + export const imageProps = { - src: IxPropTypes.string.def(''), - width: IxPropTypes.oneOfType([String, Number]), - height: IxPropTypes.oneOfType([String, Number]), - preview: IxPropTypes.bool.def(false), - fallback: IxPropTypes.string, - alt: IxPropTypes.string.def(''), - objectFit: IxPropTypes.string.def('fill'), + src: IxPropTypes.string.isRequired, + preview: IxPropTypes.bool, + imageViewer: IxPropTypes.shape({ ...imageViewerProps, images: IxPropTypes.array() }), + onLoad: IxPropTypes.emit<(e: Event) => void>(), + onError: IxPropTypes.emit<(e: Event) => void>(), } export type ImageProps = IxInnerPropTypes @@ -27,13 +43,16 @@ export type ImagePublicProps = IxPublicPropTypes export type ImageComponent = DefineComponent & ImagePublicProps> export type ImageInstance = InstanceType> -export const imagePreviewProps = { - previewSrc: IxPropTypes.string.def(''), -} - -export type ImagePreviewProps = IxInnerPropTypes -export type ImagePreviewPublicProps = IxPublicPropTypes -export type ImagePreviewComponent = DefineComponent< - Omit & ImagePreviewPublicProps +export type ImageViewerProps = IxInnerPropTypes +export type ImageViewerPublicProps = IxPublicPropTypes +export type ImageViewerComponent = DefineComponent< + Omit & ImageViewerPublicProps > -export type ImagePreviewInstance = InstanceType> +export type ImageViewerInstance = InstanceType> + +// private +export const imageViewerContentProps = { + mergedPrefixCls: IxPropTypes.string.isRequired, + ...imageViewerProps, +} +export type ImageViewerContentProps = IxInnerPropTypes diff --git a/packages/components/image/style/image.less b/packages/components/image/style/image.less new file mode 100644 index 000000000..016ded336 --- /dev/null +++ b/packages/components/image/style/image.less @@ -0,0 +1,78 @@ +@import '../../style/mixins/reset.less'; + +.@{image-prefix} { + .reset-component(); + + position: relative; + display: inline-flex; + min-width: @image-min-width; + min-height: @image-min-height; + + &-layer { + position: absolute; + width: 100%; + height: 100%; + z-index: @image-layer-z-index; + display: flex; + justify-content: center; + align-items: center; + + .@{image-prefix}-placeholder, .@{image-prefix}-fallback { + font-size: 32px; + } + } + + &-failed { + .@{image-prefix}-layer { + background-color: @color-grey-l30; + } + } + + &-preview:not(.@{image-prefix}-failed) { + .@{image-prefix}-preview-wrapper { + cursor: pointer; + display: none; + + .@{image-prefix}-preview-icon { + color: @image-preview-icon-color; + font-size: @image-preview-icon-font-size; + } + } + + + &::after { + transition: all .3s; + content: ''; + display: inline-block; + height: 100%; + width: 100%; + position: absolute; + z-index: @image-preview-bg-z-index; + background-color: @image-preview-bg-color; + left: 0; + top: 0; + opacity: 0; + } + + &:hover { + .@{image-prefix}-preview-wrapper { + display: inline-block; + } + + &::after { + opacity: 1; + } + } + } + + &-inner { + min-height: 100%; + min-width: 100%; + object-fit: @image-object-fit; + + &-hidden { + visibility: hidden; + } + } +} + diff --git a/packages/components/image/style/index.less b/packages/components/image/style/index.less deleted file mode 100644 index 52bd23ec4..000000000 --- a/packages/components/image/style/index.less +++ /dev/null @@ -1,75 +0,0 @@ -@import '../../style/mixins/reset.less'; - -.ix-image-preview-base-position(@zIndex,@top) { - position: fixed; - left: 0; - right: 0; - top: @top; - bottom: 0; - z-index: @zIndex; -} - -.@{image-prefix} { - .reset-component(); - - &-img.@{image-preview-prefix}-is { - cursor: pointer; - } -} - -.@{image-preview-prefix} { - cursor: default; - user-select: none; - - &-mask { - .ix-image-preview-base-position(@image-zindex,0); - - background-color: @image-mask-bg; - height: @image-height; - } - - &-tools { - .ix-image-preview-base-position(@image-zindex + 1,0); - - height: @image-tools-h; - background: @image-tools-bg; - - .ix-preview-tools { - display: flex; - align-items: center; - justify-content: flex-end; - list-style: none; - height: @image-height; - - .ix-tools-item { - padding: 0 12px; - cursor: pointer; - color: #fff; - - &:not(:first-child) { - margin-left: 12px; - } - } - - .ix-tools-item-disabled { - color: rgb(255 255 255 / 25%); - pointer-events: none; - cursor: not-allowed; - } - } - } - - .ix-image-preview-img { - .ix-image-preview-base-position(@image-zindex,@image-tools-h); - - height: @image-height; - display: flex; - justify-content: center; - - img { - height: @image-height; - object-fit: none; - transition: transform 0.3s cubic-bezier(0.215, 0.61, 0.355, 1) 0s; - } - } -} diff --git a/packages/components/image/style/themes/default.less b/packages/components/image/style/themes/default.less index 329463be6..9b5cfb703 100644 --- a/packages/components/image/style/themes/default.less +++ b/packages/components/image/style/themes/default.less @@ -1,3 +1,3 @@ -@import '../../../style/themes/default.less'; -@import '../index.less'; +@import '../image.less'; +@import '../viewer.less'; @import './default.variable.less'; diff --git a/packages/components/image/style/themes/default.ts b/packages/components/image/style/themes/default.ts index 8aaddc579..027ca3f89 100644 --- a/packages/components/image/style/themes/default.ts +++ b/packages/components/image/style/themes/default.ts @@ -1,5 +1,4 @@ // style dependencies import '@idux/components/style/core/default' -import '@idux/components/icon/style/themes/default' import './default.less' diff --git a/packages/components/image/style/themes/default.variable.less b/packages/components/image/style/themes/default.variable.less index 697660feb..b99b21f8c 100644 --- a/packages/components/image/style/themes/default.variable.less +++ b/packages/components/image/style/themes/default.variable.less @@ -1,6 +1,25 @@ -@image-zindex: @zindex-l4-6; -@image-mask-bg: ~'rgba(0, 0, 0, 0.45)'; -@image-tools-bg: ~'rgba(0, 0, 0, 0.1)'; -@image-height: 100%; -@image-width: 100%; -@image-tools-h: 46px; +@import '../../../style/themes/default.less'; + +@image-min-width: 96px; +@image-min-height: 96px; +@image-layer-z-index: @zindex-l1-2; +@image-object-fit: contain; +@image-preview-bg-color: rgba(0, 0, 0, 0.5); +@image-preview-bg-z-index: @zindex-l1-1; +@image-preview-icon-color: @color-white; +@image-preview-icon-font-size: @font-size-xl; + +@image-viewer-bg-color: rgba(0, 0, 0, 0.45); +@image-viewer-z-index: @zindex-l4-6; +@image-viewer-opr-color: @color-white; +@image-viewer-opr-z-index: @zindex-l1-1; +@image-viewer-opr-disabled-color: rgba(255, 255, 255, 0.35); +@image-viewer-opr-height: 48px; +@image-viewer-opr-bottom: 48px; +@image-viewer-opr-font-size: @font-size-xl; +@image-viewer-opr-bg-color: rgba(0, 0, 0, 0.1); +@image-viewer-opr-border-radius: calc(@image-viewer-opr-height / 2); +@image-viewer-opr-item-margin: 0 24px; + +@image-viewer-preview-img-max-width: 100%; +@image-viewer-preview-img-max-height: 100%; diff --git a/packages/components/image/style/viewer.less b/packages/components/image/style/viewer.less new file mode 100644 index 000000000..f6a31fbc1 --- /dev/null +++ b/packages/components/image/style/viewer.less @@ -0,0 +1,56 @@ +@import '../../style/mixins/reset.less'; +@import '../../style/motion/zoom.less'; + +.@{image-viewer-prefix} { + .reset-component(); + + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + background: @image-viewer-bg-color; + user-select: none; + z-index: @image-viewer-z-index; + + .@{image-viewer-prefix}-opr { + position: absolute; + z-index: @image-viewer-opr-z-index; + bottom: @image-viewer-opr-bottom; + left: 50%; + transform: translateX(-50%); + display: flex; + justify-content: space-around; + align-items: center; + border-radius: @image-viewer-opr-border-radius; + height: @image-viewer-opr-height; + color: @image-viewer-opr-color; + font-size: @image-viewer-opr-font-size; + background-color: @image-viewer-opr-bg-color; + + &-item { + margin: @image-viewer-opr-item-margin; + cursor: pointer; + + &-disabled { + pointer-events: none; + color: @image-viewer-opr-disabled-color; + } + } + } + + .@{image-viewer-prefix}-preview { + width: 100%; + height: 100%;; + display: flex; + align-items: center; + justify-content: center; + + &-img { + transition: all .3s; + will-change: transform; + max-height: @image-viewer-preview-img-max-height; + max-width: @image-viewer-preview-img-max-width; + } + } +} diff --git a/packages/components/index.ts b/packages/components/index.ts index fbb70e600..e71d467cf 100644 --- a/packages/components/index.ts +++ b/packages/components/index.ts @@ -28,7 +28,7 @@ import { IxForm, IxFormItem, IxFormWrapper } from '@idux/components/form' import { IxCol, IxRow } from '@idux/components/grid' import { IxHeader } from '@idux/components/header' import { IxIcon } from '@idux/components/icon' -import { IxImage } from '@idux/components/image' +import { IxImage, IxImageViewer } from '@idux/components/image' import { IxInput } from '@idux/components/input' import { IxInputNumber } from '@idux/components/input-number' import { IxLayout, IxLayoutContent, IxLayoutFooter, IxLayoutHeader, IxLayoutSider } from '@idux/components/layout' @@ -97,6 +97,7 @@ const components = [ IxHeader, IxIcon, IxImage, + IxImageViewer, IxInput, IxInputNumber, IxLayout, diff --git a/packages/components/style/variable/prefix.less b/packages/components/style/variable/prefix.less index d37fa98e8..e9b5bef29 100644 --- a/packages/components/style/variable/prefix.less +++ b/packages/components/style/variable/prefix.less @@ -33,7 +33,7 @@ @collapse-panel-prefix: ~'@{idux-prefix}-collapse-panel'; @empty-prefix: ~'@{idux-prefix}-empty'; @image-prefix: ~'@{idux-prefix}-image'; -@image-preview-prefix: ~'@{image-prefix}-preview'; +@image-viewer-prefix: ~'@{image-prefix}-viewer'; @statistic-prefix: ~'@{idux-prefix}-statistic'; @timeline-prefix: ~'@{idux-prefix}-timeline'; @timeline-item-prefix: ~'@{idux-prefix}-timeline-item'; diff --git a/packages/components/types.d.ts b/packages/components/types.d.ts index 85136e6cf..b1df17dd6 100644 --- a/packages/components/types.d.ts +++ b/packages/components/types.d.ts @@ -25,7 +25,7 @@ import type { FormComponent, FormItemComponent, FormWrapperComponent } from '@id import type { ColComponent, RowComponent } from '@idux/components/grid' import type { HeaderComponent } from '@idux/components/header' import type { IconComponent } from '@idux/components/icon' -import type { ImageComponent } from '@idux/components/image' +import type { ImageComponent, ImageViewerComponent } from '@idux/components/image' import type { InputComponent } from '@idux/components/input' import type { InputNumberComponent } from '@idux/components/input-number' import type { @@ -102,6 +102,7 @@ declare module 'vue' { IxHeader: HeaderComponent IxIcon: IconComponent IxImage: ImageComponent + IxImageViewer: ImageViewerComponent IxInput: InputComponent IxInputNumber: InputNumberComponent IxLayout: LayoutComponent diff --git a/packages/site/public/images/example/css.png b/packages/site/public/images/example/css.png new file mode 100644 index 0000000000000000000000000000000000000000..3b433a9229c8c12f99f2d19424582949e4884ffe GIT binary patch literal 14877 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 literal 0 HcmV?d00001 diff --git a/packages/site/public/images/example/html.png b/packages/site/public/images/example/html.png new file mode 100644 index 0000000000000000000000000000000000000000..de61756cc93f3d9a3066447ac943418b41843d0b GIT binary patch literal 13313 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
  • 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 literal 0 HcmV?d00001 diff --git a/packages/site/public/images/example/js.png b/packages/site/public/images/example/js.png new file mode 100644 index 0000000000000000000000000000000000000000..285758d147558fcfec7024e9490590e3682ca9be GIT binary patch literal 44629 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 QMBt~RsjpF{X8ZjA0M%atd;kCd literal 0 HcmV?d00001 diff --git a/packages/site/public/images/example/nodejs.png b/packages/site/public/images/example/nodejs.png new file mode 100644 index 0000000000000000000000000000000000000000..a630f530ec82657e7fec8f2309249e22ba6cb8ed GIT binary patch literal 21808 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 literal 0 HcmV?d00001