feat: 统一图片铺满展示与原生预览
This commit is contained in:
@@ -90,7 +90,7 @@ Oxfmt 不负责代码质量,Oxlint 不负责 Vue 的完整类型推导;Vue S
|
|||||||
- 项目列表采用固定表头表格;剧本、分镜、身份与生产页的目录和详情独立滚动,其他工作区只滚动内容区。
|
- 项目列表采用固定表头表格;剧本、分镜、身份与生产页的目录和详情独立滚动,其他工作区只滚动内容区。
|
||||||
- 目录、详情、执行记录、折叠操作区、图片历史和 JSON 查看区统一使用 Naive UI `NScrollbar`;外层只负责尺寸与裁切,不再设置原生 `overflow: auto`。内边距与横向排列放在 `content-class` 内容层,避免双滚动条。
|
- 目录、详情、执行记录、折叠操作区、图片历史和 JSON 查看区统一使用 Naive UI `NScrollbar`;外层只负责尺寸与裁切,不再设置原生 `overflow: auto`。内边距与横向排列放在 `content-class` 内容层,避免双滚动条。
|
||||||
- 身份与形态图库采用限高预览及紧邻其下的横向历史栏;缩略图固定宽度,保留全部候选、辅助视图、失败及进行中记录。点击仅切换预览,不自动切换母版或主图;图片尺寸规则放在非分层组件覆盖样式中,避免被 Naive 按钮默认宽度覆盖。
|
- 身份与形态图库采用限高预览及紧邻其下的横向历史栏;缩略图固定宽度,保留全部候选、辅助视图、失败及进行中记录。点击仅切换预览,不自动切换母版或主图;图片尺寸规则放在非分层组件覆盖样式中,避免被 Naive 按钮默认宽度覆盖。
|
||||||
- 形态图库卡片采用 `cover` 铺满,点击图片调用 `NImage` 原生预览查看完整原图;“查看图片与记录”仍单独打开历史管理。历史缩略图和其他资源默认保持 `contain` 且不触发原图预览,避免与候选选择混淆。
|
- 身份母版、图库大图、历史缩略图、形态/风格/分镜参考图及首帧统一通过 `NImage` 采用 `cover` 铺满。母版与展示图片可点击原生预览查看完整原图;历史缩略图只切换选中记录,再点击大图查看原图,避免与候选选择混淆。“查看图片与记录”仍单独打开历史管理。
|
||||||
- 拆解页固定精简状态栏与结果标签,设置、预览及恢复操作收进“拆解设置与恢复”面板;人物、场景、道具、分镜和任务明细共用独立结果滚动区,执行记录单独滚动。结果区占满剩余高度,切换标签回到顶部,轮询刷新或开关设置不重置阅读位置;失败与校验提示在面板关闭时仍然可见。
|
- 拆解页固定精简状态栏与结果标签,设置、预览及恢复操作收进“拆解设置与恢复”面板;人物、场景、道具、分镜和任务明细共用独立结果滚动区,执行记录单独滚动。结果区占满剩余高度,切换标签回到顶部,轮询刷新或开关设置不重置阅读位置;失败与校验提示在面板关闭时仍然可见。
|
||||||
- 剧集正文按编号连续展示全部已写入剧集;点击目录定位正文,滚动正文同步高亮并保持当前目录项可见。目录分行显示集数和完整标题,执行记录可按需展开。切换角色/世界观等标签不会销毁阅读器或重置位置。
|
- 剧集正文按编号连续展示全部已写入剧集;点击目录定位正文,滚动正文同步高亮并保持当前目录项可见。目录分行显示集数和完整标题,执行记录可按需展开。切换角色/世界观等标签不会销毁阅读器或重置位置。
|
||||||
- 主体身份、分镜设计和镜头生产共用 `DirectoryItem`:编号、完整标题和状态分层展示,桌面目录为 240–280px,窄屏改为可横向滚动的条目。目录标题与筛选固定,只有列表滚动。拆解结果的导出按钮与标签同排右对齐;形态图片筛选按内容区宽度自动排成一行或两列网格。
|
- 主体身份、分镜设计和镜头生产共用 `DirectoryItem`:编号、完整标题和状态分层展示,桌面目录为 240–280px,窄屏改为可横向滚动的条目。目录标题与筛选固定,只有列表滚动。拆解结果的导出按钮与标签同排右对齐;形态图片筛选按内容区宽度自动排成一行或两列网格。
|
||||||
|
|||||||
@@ -899,7 +899,6 @@ body {
|
|||||||
.asset-image .n-image img {
|
.asset-image .n-image img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: contain;
|
|
||||||
}
|
}
|
||||||
/* 预览尺寸不随原图分辨率增长,历史缩略图也不能被按钮默认宽度覆盖。 */
|
/* 预览尺寸不随原图分辨率增长,历史缩略图也不能被按钮默认宽度覆盖。 */
|
||||||
.asset-image.asset-image-preview {
|
.asset-image.asset-image-preview {
|
||||||
|
|||||||
@@ -11,17 +11,26 @@ afterEach(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('资源图片展示与原图预览', () => {
|
describe('资源图片展示与原图预览', () => {
|
||||||
it('默认保持 contain 并禁用预览,不干扰历史缩略图选择', async () => {
|
it('默认使用 cover 并禁用预览,不干扰历史缩略图选择', async () => {
|
||||||
wrapper = mount(AssetImage, {
|
wrapper = mount(AssetImage, {
|
||||||
attachTo: document.body,
|
attachTo: document.body,
|
||||||
props: { src: '/storage/history.png', alt: '历史图片' }
|
props: { src: '/storage/history.png', alt: '历史图片' }
|
||||||
})
|
})
|
||||||
expect(wrapper.getComponent(NImage).props()).toMatchObject({ objectFit: 'contain', previewDisabled: true })
|
expect(wrapper.getComponent(NImage).props()).toMatchObject({ objectFit: 'cover', previewDisabled: true })
|
||||||
|
expect(wrapper.get<HTMLImageElement>('img').element.style.objectFit).toBe('cover')
|
||||||
await wrapper.get('img').trigger('click')
|
await wrapper.get('img').trigger('click')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(document.querySelector('.n-image-preview-container')).toBeNull()
|
expect(document.querySelector('.n-image-preview-container')).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('特殊展示仍可显式选择 contain,样式交由 NImage 控制', () => {
|
||||||
|
wrapper = mount(AssetImage, {
|
||||||
|
props: { src: '/storage/reference.png', alt: '完整参考图', objectFit: 'contain' }
|
||||||
|
})
|
||||||
|
expect(wrapper.getComponent(NImage).props('objectFit')).toBe('contain')
|
||||||
|
expect(wrapper.get<HTMLImageElement>('img').element.style.objectFit).toBe('contain')
|
||||||
|
})
|
||||||
|
|
||||||
it('cover 仅裁切卡片,点击使用 Naive 原生预览展示同一完整资源,遮罩可关闭', async () => {
|
it('cover 仅裁切卡片,点击使用 Naive 原生预览展示同一完整资源,遮罩可关闭', async () => {
|
||||||
wrapper = mount(AssetImage, {
|
wrapper = mount(AssetImage, {
|
||||||
attachTo: document.body,
|
attachTo: document.body,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { computed, ref, watch } from 'vue'
|
|||||||
import { ImageOff } from '@lucide/vue'
|
import { ImageOff } from '@lucide/vue'
|
||||||
import { referenceImageUrl } from '../../lib/assets'
|
import { referenceImageUrl } from '../../lib/assets'
|
||||||
|
|
||||||
/** 安全图片展示;铺满与原图预览按场景开启,历史选择按钮默认不触发预览。 */
|
/** 图片统一铺满容器;原图预览按场景开启,历史选择按钮不触发预览。 */
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
src?: string | null
|
src?: string | null
|
||||||
@@ -14,7 +14,7 @@ const props = withDefaults(
|
|||||||
objectFit?: 'contain' | 'cover'
|
objectFit?: 'contain' | 'cover'
|
||||||
preview?: boolean
|
preview?: boolean
|
||||||
}>(),
|
}>(),
|
||||||
{ retryable: true, objectFit: 'contain', preview: false }
|
{ retryable: true, objectFit: 'cover', preview: false }
|
||||||
)
|
)
|
||||||
const failed = ref(false)
|
const failed = ref(false)
|
||||||
const url = computed(() => (props.src ? referenceImageUrl(props.src) : null))
|
const url = computed(() => (props.src ? referenceImageUrl(props.src) : null))
|
||||||
|
|||||||
@@ -260,6 +260,7 @@ async function inspectSpecs() {
|
|||||||
<AssetImage
|
<AssetImage
|
||||||
:src="item.status === 'completed' ? item.imageUrl : null"
|
:src="item.status === 'completed' ? item.imageUrl : null"
|
||||||
:alt="`镜头 ${shot.shotNo} 首帧`"
|
:alt="`镜头 ${shot.shotNo} 首帧`"
|
||||||
|
preview
|
||||||
:empty-text="item.status === 'failed' ? '首帧生成失败' : '首帧生成中'"
|
:empty-text="item.status === 'failed' ? '首帧生成失败' : '首帧生成中'"
|
||||||
/>
|
/>
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { NScrollbar, NAlert, NButton, NCheckbox } from 'naive-ui'
|
import { NScrollbar, NAlert, NButton, NCheckbox } from 'naive-ui'
|
||||||
import { computed, onScopeDispose, ref, watch } from 'vue'
|
import { computed, onScopeDispose, ref, watch } from 'vue'
|
||||||
import { Download, ExternalLink, RefreshCw } from '@lucide/vue'
|
import { Download, ExternalLink, RefreshCw } from '@lucide/vue'
|
||||||
|
import { AssetImage } from '../../../components/ui'
|
||||||
import { storyboardApi } from '../api'
|
import { storyboardApi } from '../api'
|
||||||
import { getStoryboardSession, referenceImageUrl } from '../model'
|
import { getStoryboardSession, referenceImageUrl } from '../model'
|
||||||
import type { DesignedShot, ShotReferences } from '../types'
|
import type { DesignedShot, ShotReferences } from '../types'
|
||||||
@@ -17,7 +18,6 @@ const references = ref<ShotReferences | null>(null)
|
|||||||
const spec = ref<ShotGenerationSpec | null>(null)
|
const spec = ref<ShotGenerationSpec | null>(null)
|
||||||
const errors = ref({ references: '', spec: '' })
|
const errors = ref({ references: '', spec: '' })
|
||||||
const busy = ref({ references: false, spec: false })
|
const busy = ref({ references: false, spec: false })
|
||||||
const brokenImages = ref<string[]>([])
|
|
||||||
const force = ref(false)
|
const force = ref(false)
|
||||||
const prompt = computed(() => getStoryboardSession(props.projectId).prompts[props.shot.shotId])
|
const prompt = computed(() => getStoryboardSession(props.projectId).prompts[props.shot.shotId])
|
||||||
const operation = computed(() => getOperation(props.projectId))
|
const operation = computed(() => getOperation(props.projectId))
|
||||||
@@ -33,7 +33,6 @@ function reset() {
|
|||||||
spec.value = null
|
spec.value = null
|
||||||
errors.value = { references: '', spec: '' }
|
errors.value = { references: '', spec: '' }
|
||||||
busy.value = { references: false, spec: false }
|
busy.value = { references: false, spec: false }
|
||||||
brokenImages.value = []
|
|
||||||
}
|
}
|
||||||
watch(() => JSON.stringify([props.projectId, props.shot.shotId, props.shot.direction, props.shot.visualState]), reset)
|
watch(() => JSON.stringify([props.projectId, props.shot.shotId, props.shot.direction, props.shot.visualState]), reset)
|
||||||
onScopeDispose(reset)
|
onScopeDispose(reset)
|
||||||
@@ -54,7 +53,6 @@ async function load(kind: 'references' | 'spec') {
|
|||||||
if (version !== revision) return
|
if (version !== revision) return
|
||||||
if (result.shotId !== shotId) throw new Error('参考图返回的 Shot ID 不匹配。')
|
if (result.shotId !== shotId) throw new Error('参考图返回的 Shot ID 不匹配。')
|
||||||
references.value = result
|
references.value = result
|
||||||
brokenImages.value = []
|
|
||||||
} else {
|
} else {
|
||||||
const result = await storyboardApi.generationSpec(shotId, controller.signal)
|
const result = await storyboardApi.generationSpec(shotId, controller.signal)
|
||||||
if (version !== revision) return
|
if (version !== revision) return
|
||||||
@@ -117,20 +115,13 @@ function exportResult(kind: 'spec' | 'prompt') {
|
|||||||
<template v-if="references">
|
<template v-if="references">
|
||||||
<div class="reference-grid mt-4">
|
<div class="reference-grid mt-4">
|
||||||
<article v-for="item in references.references" :key="item.shotSubjectId" class="reference-card">
|
<article v-for="item in references.references" :key="item.shotSubjectId" class="reference-card">
|
||||||
<a
|
<AssetImage
|
||||||
v-if="referenceImageUrl(item.imageUrl) && !brokenImages.includes(item.imageId)"
|
:key="item.imageId"
|
||||||
:href="referenceImageUrl(item.imageUrl)!"
|
:src="referenceImageUrl(item.imageUrl)"
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="block"
|
|
||||||
><img
|
|
||||||
:src="referenceImageUrl(item.imageUrl)!"
|
|
||||||
:alt="`${item.subjectName} · ${item.subjectFormName}`"
|
:alt="`${item.subjectName} · ${item.subjectFormName}`"
|
||||||
loading="lazy"
|
empty-text="图片不可用"
|
||||||
referrerpolicy="no-referrer"
|
preview
|
||||||
@error="brokenImages.push(item.imageId)"
|
/>
|
||||||
/></a>
|
|
||||||
<div v-else class="reference-placeholder">图片不可用</div>
|
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
<p class="text-xs font-medium">{{ item.subjectName }} · {{ item.subjectFormName }}</p>
|
<p class="text-xs font-medium">{{ item.subjectName }} · {{ item.subjectFormName }}</p>
|
||||||
<code class="mt-1 block text-[10px] text-muted">{{ item.subjectRef }}</code
|
<code class="mt-1 block text-[10px] text-muted">{{ item.subjectRef }}</code
|
||||||
|
|||||||
@@ -527,6 +527,7 @@ watch(
|
|||||||
<AssetImage
|
<AssetImage
|
||||||
:src="anchor.imageUrl"
|
:src="anchor.imageUrl"
|
||||||
:alt="`${subject.name}当前身份母版`"
|
:alt="`${subject.name}当前身份母版`"
|
||||||
|
preview
|
||||||
class="h-20 w-20 shrink-0 rounded"
|
class="h-20 w-20 shrink-0 rounded"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ function chooseAnchor() {
|
|||||||
:src="selected.status === 'completed' ? selected.imageUrl : null"
|
:src="selected.status === 'completed' ? selected.imageUrl : null"
|
||||||
:alt="`${subjectName} · ${identityViewLabels[selected.viewType]}`"
|
:alt="`${subjectName} · ${identityViewLabels[selected.viewType]}`"
|
||||||
class="asset-image-preview mt-4"
|
class="asset-image-preview mt-4"
|
||||||
|
preview
|
||||||
:empty-text="selected.status === 'failed' ? '本次生成失败' : '等待后端图片结果'"
|
:empty-text="selected.status === 'failed' ? '本次生成失败' : '等待后端图片结果'"
|
||||||
/>
|
/>
|
||||||
<div class="image-history-heading">
|
<div class="image-history-heading">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { flushPromises, mount, type VueWrapper } from '@vue/test-utils'
|
import { flushPromises, mount, type VueWrapper } from '@vue/test-utils'
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { NScrollbar } from 'naive-ui'
|
import { NImage, NScrollbar } from 'naive-ui'
|
||||||
import { AssetImage } from '../../components/ui'
|
import { AssetImage } from '../../components/ui'
|
||||||
import IdentityImageDialog from './components/IdentityImageDialog.vue'
|
import IdentityImageDialog from './components/IdentityImageDialog.vue'
|
||||||
import IdentityGallery from './components/IdentityGallery.vue'
|
import IdentityGallery from './components/IdentityGallery.vue'
|
||||||
@@ -45,6 +45,43 @@ function input(selector: string, value: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('身份图与母版契约', () => {
|
describe('身份图与母版契约', () => {
|
||||||
|
it('大图和历史统一 cover,缩略图只切换记录,大图单独打开完整原图', async () => {
|
||||||
|
wrapper = mount(IdentityGallery, {
|
||||||
|
attachTo: document.body,
|
||||||
|
props: {
|
||||||
|
subjectName: '林夏',
|
||||||
|
module: 'character',
|
||||||
|
identityLocked: true,
|
||||||
|
disabled: false,
|
||||||
|
images: [
|
||||||
|
identityImageFixture({ id: 'anchor', imageUrl: '/storage/anchor.png' }),
|
||||||
|
identityImageFixture({
|
||||||
|
id: 'front',
|
||||||
|
viewType: 'front',
|
||||||
|
isAnchor: false,
|
||||||
|
imageUrl: '/storage/front.png'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const images = wrapper.findAllComponents(NImage)
|
||||||
|
expect(images).toHaveLength(3)
|
||||||
|
expect(images.every(image => image.props('objectFit') === 'cover')).toBe(true)
|
||||||
|
expect(images.map(image => image.props('previewDisabled'))).toEqual([false, true, true])
|
||||||
|
await wrapper.get('[aria-label="查看身份图片 front"] img').trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.get('[aria-label="查看身份图片 front"]').attributes('aria-pressed')).toBe('true')
|
||||||
|
expect(document.querySelector('.n-image-preview-container')).toBeNull()
|
||||||
|
const main = wrapper.get('.asset-image-preview img')
|
||||||
|
expect(main.attributes('src')).toContain('/storage/front.png')
|
||||||
|
await main.trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
const original = document.querySelector<HTMLImageElement>('.n-image-preview')!
|
||||||
|
expect(original.getAttribute('src')).toBe(main.attributes('src'))
|
||||||
|
expect(original.style.objectFit).not.toBe('cover')
|
||||||
|
expect(wrapper.emitted('anchor')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
it('实际提示词默认展开,可手动收起,轮询更新不强行重新展开', async () => {
|
it('实际提示词默认展开,可手动收起,轮询更新不强行重新展开', async () => {
|
||||||
const image = identityImageFixture({ prompt: '本次实际生成提示词' })
|
const image = identityImageFixture({ prompt: '本次实际生成提示词' })
|
||||||
wrapper = mount(IdentityGallery, {
|
wrapper = mount(IdentityGallery, {
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ async function choosePrimary() {
|
|||||||
:alt="`${form?.subject.name} · ${form?.name}`"
|
:alt="`${form?.subject.name} · ${form?.name}`"
|
||||||
:empty-text="selected.status === 'failed' ? '本次生成失败' : '等待后端生成结果'"
|
:empty-text="selected.status === 'failed' ? '本次生成失败' : '等待后端生成结果'"
|
||||||
class="asset-image-preview"
|
class="asset-image-preview"
|
||||||
|
preview
|
||||||
/>
|
/>
|
||||||
<div class="image-history-heading">
|
<div class="image-history-heading">
|
||||||
<h4 class="font-medium">历史记录 · {{ images.length }} 条</h4>
|
<h4 class="font-medium">历史记录 · {{ images.length }} 条</h4>
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ function remove(image: VisualStyleImage) {
|
|||||||
:src="image.imageUrl"
|
:src="image.imageUrl"
|
||||||
:alt="`${categories[image.category]}风格参考图`"
|
:alt="`${categories[image.category]}风格参考图`"
|
||||||
class="aspect-square"
|
class="aspect-square"
|
||||||
|
preview
|
||||||
/>
|
/>
|
||||||
<p class="mt-3 text-xs">
|
<p class="mt-3 text-xs">
|
||||||
{{ categories[image.category] }} · {{ image.enabled ? '已启用' : '已停用' }} · 排序
|
{{ categories[image.category] }} · {{ image.enabled ? '已启用' : '已停用' }} · 排序
|
||||||
|
|||||||
@@ -525,6 +525,10 @@ describe('视觉风格与主体身份工作区', () => {
|
|||||||
'/api/subjects/subject-db-1/identity/images/candidate/anchor'
|
'/api/subjects/subject-db-1/identity/images/candidate/anchor'
|
||||||
)
|
)
|
||||||
expect(wrapper!.text()).toContain('当前身份母版')
|
expect(wrapper!.text()).toContain('当前身份母版')
|
||||||
|
const anchorImage = wrapper!
|
||||||
|
.findAllComponents(NImage)
|
||||||
|
.find(image => image.props('alt')?.endsWith('当前身份母版'))!
|
||||||
|
expect(anchorImage.props()).toMatchObject({ objectFit: 'cover', previewDisabled: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('选角进度卡片分层显示长姓名、编号和状态,点击仍按正式 ID 切换主体', async () => {
|
it('选角进度卡片分层显示长姓名、编号和状态,点击仍按正式 ID 切换主体', async () => {
|
||||||
@@ -874,6 +878,41 @@ describe('工作台页面交互', () => {
|
|||||||
expect(wrapper!.find('.form-image-card img').exists()).toBe(true)
|
expect(wrapper!.find('.form-image-card img').exists()).toBe(true)
|
||||||
expect(button('补齐项目主参考图').disabled).toBe(true)
|
expect(button('补齐项目主参考图').disabled).toBe(true)
|
||||||
})
|
})
|
||||||
|
it('分镜参考图使用 cover 和原生预览,查看图片不触发生成功能', async () => {
|
||||||
|
const shot = designedShot()
|
||||||
|
const fetcher = vi.fn<typeof fetch>().mockResolvedValue(
|
||||||
|
jsonResponse({
|
||||||
|
shotId: shot.shotId,
|
||||||
|
references: [
|
||||||
|
{
|
||||||
|
shotSubjectId: 'binding-db',
|
||||||
|
subjectId: 'subject-db',
|
||||||
|
subjectRef: '@CH0001',
|
||||||
|
subjectName: '林知夏',
|
||||||
|
module: 'character',
|
||||||
|
subjectFormId: 'form-db',
|
||||||
|
subjectFormName: '日常',
|
||||||
|
imageId: 'image-db',
|
||||||
|
imageUrl: '/storage/reference.png'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
missing: []
|
||||||
|
})
|
||||||
|
)
|
||||||
|
vi.stubGlobal('fetch', fetcher)
|
||||||
|
wrapper = mount(ShotTools, { attachTo: document.body, props: { projectId: fixture.id, shot, disabled: false } })
|
||||||
|
button('读取参考图').click()
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.getComponent(NImage).props()).toMatchObject({ objectFit: 'cover', previewDisabled: false })
|
||||||
|
const image = wrapper.get('.reference-card img')
|
||||||
|
expect(image.element.parentElement?.closest('a')).toBeNull()
|
||||||
|
await image.trigger('click')
|
||||||
|
await flushPromises()
|
||||||
|
expect(document.querySelector('.n-image-preview')?.getAttribute('src')).toBe(image.attributes('src'))
|
||||||
|
expect(fetcher.mock.calls).toHaveLength(1)
|
||||||
|
expect(fetcher.mock.calls.every(([, init]) => !init?.method || init.method === 'GET')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
it('参考图拦截危险地址,生成规格只通过 GET 读取正式形态和状态', async () => {
|
it('参考图拦截危险地址,生成规格只通过 GET 读取正式形态和状态', async () => {
|
||||||
const shot = designedShot()
|
const shot = designedShot()
|
||||||
const fetcher = vi
|
const fetcher = vi
|
||||||
|
|||||||
@@ -482,7 +482,6 @@
|
|||||||
.asset-image img {
|
.asset-image img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: contain;
|
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
.asset-image-empty {
|
.asset-image-empty {
|
||||||
@@ -548,12 +547,6 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.reference-card img {
|
|
||||||
width: 100%;
|
|
||||||
aspect-ratio: 4 / 3;
|
|
||||||
object-fit: contain;
|
|
||||||
background: var(--app-subtle);
|
|
||||||
}
|
|
||||||
.reference-placeholder {
|
.reference-placeholder {
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user