feat: 形态图库支持铺满展示与原图预览
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { flushPromises, mount, type VueWrapper } from '@vue/test-utils'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { NImage } from 'naive-ui'
|
||||
import AssetImage from './AssetImage.vue'
|
||||
|
||||
let wrapper: VueWrapper | undefined
|
||||
afterEach(() => {
|
||||
wrapper?.unmount()
|
||||
wrapper = undefined
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
||||
describe('资源图片展示与原图预览', () => {
|
||||
it('默认保持 contain 并禁用预览,不干扰历史缩略图选择', async () => {
|
||||
wrapper = mount(AssetImage, {
|
||||
attachTo: document.body,
|
||||
props: { src: '/storage/history.png', alt: '历史图片' }
|
||||
})
|
||||
expect(wrapper.getComponent(NImage).props()).toMatchObject({ objectFit: 'contain', previewDisabled: true })
|
||||
await wrapper.get('img').trigger('click')
|
||||
await flushPromises()
|
||||
expect(document.querySelector('.n-image-preview-container')).toBeNull()
|
||||
})
|
||||
|
||||
it('cover 仅裁切卡片,点击使用 Naive 原生预览展示同一完整资源,遮罩可关闭', async () => {
|
||||
wrapper = mount(AssetImage, {
|
||||
attachTo: document.body,
|
||||
props: { src: '/storage/original.png', alt: '林默 · 基础形态', objectFit: 'cover', preview: true }
|
||||
})
|
||||
const source = wrapper.get('img').attributes('src')
|
||||
expect(wrapper.getComponent(NImage).props()).toMatchObject({
|
||||
objectFit: 'cover',
|
||||
previewDisabled: false,
|
||||
previewSrc: source
|
||||
})
|
||||
expect(wrapper.get<HTMLImageElement>('img').element.style.objectFit).toBe('cover')
|
||||
await wrapper.get('img').trigger('click')
|
||||
await flushPromises()
|
||||
const image = document.querySelector<HTMLImageElement>('.n-image-preview')!
|
||||
expect(image).not.toBeNull()
|
||||
expect(image.getAttribute('src')).toBe(source)
|
||||
expect(image.alt).toBe('林默 · 基础形态')
|
||||
expect(image.getAttribute('referrerpolicy')).toBe('no-referrer')
|
||||
expect(image.style.objectFit).not.toBe('cover')
|
||||
expect(document.querySelector('.n-image-preview-toolbar')).not.toBeNull()
|
||||
expect(wrapper.element.contains(image)).toBe(false)
|
||||
document.querySelector<HTMLElement>('.n-image-preview-overlay')!.click()
|
||||
await flushPromises()
|
||||
expect(document.querySelector<HTMLElement>('.n-image-preview-wrapper')?.style.display ?? 'none').toBe('none')
|
||||
})
|
||||
|
||||
it('资源加载失败只显示重试占位,不打开空预览', async () => {
|
||||
wrapper = mount(AssetImage, {
|
||||
attachTo: document.body,
|
||||
props: { src: '/storage/missing.png', alt: '失败图片', objectFit: 'cover', preview: true }
|
||||
})
|
||||
await wrapper.get('img').trigger('error')
|
||||
expect(wrapper.text()).toContain('图片无法加载')
|
||||
expect(wrapper.findComponent(NImage).exists()).toBe(false)
|
||||
expect(document.querySelector('.n-image-preview-container')).toBeNull()
|
||||
await wrapper.get('button').trigger('click')
|
||||
expect(wrapper.getComponent(NImage).props('previewDisabled')).toBe(false)
|
||||
})
|
||||
|
||||
it.each([null, 'javascript:alert(1)', 'data:text/html,test'])('空地址或危险地址 %s 不进入原图预览', src => {
|
||||
wrapper = mount(AssetImage, { props: { src, alt: '图片', objectFit: 'cover', preview: true } })
|
||||
expect(wrapper.findComponent(NImage).exists()).toBe(false)
|
||||
expect(wrapper.find('img').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -4,10 +4,17 @@ import { computed, ref, watch } from 'vue'
|
||||
import { ImageOff } from '@lucide/vue'
|
||||
import { referenceImageUrl } from '../../lib/assets'
|
||||
|
||||
/** 安全图片展示,资源失效或地址不合法时提供明确占位,不自动改用临时 Provider URL。 */
|
||||
/** 安全图片展示;铺满与原图预览按场景开启,历史选择按钮默认不触发预览。 */
|
||||
const props = withDefaults(
|
||||
defineProps<{ src?: string | null; alt: string; emptyText?: string; retryable?: boolean }>(),
|
||||
{ retryable: true }
|
||||
defineProps<{
|
||||
src?: string | null
|
||||
alt: string
|
||||
emptyText?: string
|
||||
retryable?: boolean
|
||||
objectFit?: 'contain' | 'cover'
|
||||
preview?: boolean
|
||||
}>(),
|
||||
{ retryable: true, objectFit: 'contain', preview: false }
|
||||
)
|
||||
const failed = ref(false)
|
||||
const url = computed(() => (props.src ? referenceImageUrl(props.src) : null))
|
||||
@@ -24,11 +31,13 @@ watch(
|
||||
<NImage
|
||||
v-if="url && !failed"
|
||||
:src="url"
|
||||
:preview-src="url"
|
||||
:alt="alt"
|
||||
lazy
|
||||
preview-disabled
|
||||
object-fit="contain"
|
||||
:preview-disabled="!preview"
|
||||
:object-fit="objectFit"
|
||||
:img-props="{ referrerpolicy: 'no-referrer' }"
|
||||
:previewed-img-props="{ alt, referrerpolicy: 'no-referrer' }"
|
||||
@error="failed = true"
|
||||
/>
|
||||
<span v-else class="asset-image-empty"
|
||||
|
||||
Reference in New Issue
Block a user