fix: 收紧图库镜头导航并移除空白定位横条
This commit is contained in:
+18
-1
@@ -875,6 +875,18 @@ body {
|
||||
.form-image-toolbar.has-impact-picker {
|
||||
grid-template-columns: minmax(220px, 340px) minmax(0, 1fr);
|
||||
}
|
||||
.form-image-toolbar.has-impact-picker.has-impact-actions {
|
||||
grid-template-columns: minmax(260px, 420px) minmax(0, 1fr);
|
||||
}
|
||||
.asset-impact-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
.asset-impact-picker .asset-impact-select {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
.form-image-toolbar > * {
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -904,7 +916,8 @@ body {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@container (max-width: 1200px) {
|
||||
.form-image-toolbar.has-impact-picker {
|
||||
.form-image-toolbar.has-impact-picker,
|
||||
.form-image-toolbar.has-impact-picker.has-impact-actions {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
@@ -1482,8 +1495,12 @@ body {
|
||||
}
|
||||
/* 镜头影响和素材定位使用固定底色;高亮只表示当前定位,不代表素材失效。 */
|
||||
.asset-impact-context {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 14px 16px;
|
||||
background: var(--app-subtle);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.asset-impact-select {
|
||||
min-width: 0;
|
||||
|
||||
@@ -152,8 +152,10 @@ describe('全站平面主题', () => {
|
||||
/\.form-image-toolbar\.has-impact-picker\s*\{[^}]*grid-template-columns:\s*minmax\(220px, 340px\) minmax\(0, 1fr\);/
|
||||
)
|
||||
expect(css).toMatch(
|
||||
/@container \(max-width: 1200px\)\s*\{\s*\.form-image-toolbar\.has-impact-picker\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\);/
|
||||
/@container \(max-width: 1200px\)\s*\{\s*\.form-image-toolbar\.has-impact-picker,\s*\.form-image-toolbar\.has-impact-picker\.has-impact-actions\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\);/
|
||||
)
|
||||
expect(css).toMatch(/\.asset-impact-picker\s*\{[^}]*align-items:\s*center;[^}]*gap:\s*8px;/)
|
||||
expect(css).toMatch(/\.asset-impact-context\s*\{[^}]*gap:\s*10px;[^}]*margin-bottom:\s*12px;/)
|
||||
})
|
||||
|
||||
it('单图标按钮以当前控件高度为边长,尺寸与内容按钮互不影响', () => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import WorkspaceTools from '../../components/ui/WorkspaceTools.vue'
|
||||
import { NAlert, NButton, NCheckbox, NCollapse, NCollapseItem, NInput, NInputNumber, NSelect, NTag } from 'naive-ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { Search, RefreshCw, Download, ImagePlus, ArrowRight, AlertTriangle } from '@lucide/vue'
|
||||
import { Search, RefreshCw, Download, ImagePlus, ArrowLeft, ArrowRight, LayoutGrid, AlertTriangle } from '@lucide/vue'
|
||||
import { AssetImage, EmptyState, StatusBadge } from '../../components/ui'
|
||||
import { downloadText } from '../../lib/format'
|
||||
import ConfirmAction from '../workflows/ConfirmAction.vue'
|
||||
@@ -51,6 +51,35 @@ const targetForm = computed(() => forms.value.find(form => form.id === targetFor
|
||||
const impact = useKeyframeImpact(id, sourceShotId)
|
||||
const sourceShot = impact.source
|
||||
const linkedReferences = computed(() => referenceTargets(impact.references.data.value, []))
|
||||
/** 定位说明仅在有实际状态、引用或错误时出现,避免两项导航操作单独撑起空白横条。 */
|
||||
const sourceMissing = computed(
|
||||
() => sourceShotId.value && !sourceShot.value && !impact.query.loading.value && !impact.query.error.value
|
||||
)
|
||||
const sourceCurrent = computed(
|
||||
() =>
|
||||
sourceShot.value &&
|
||||
!sourceShot.value.primaryKeyframeStale &&
|
||||
!impact.query.loading.value &&
|
||||
!impact.query.error.value
|
||||
)
|
||||
const targetUnlinked = computed(
|
||||
() =>
|
||||
targetForm.value &&
|
||||
impact.references.data.value &&
|
||||
!impact.references.error.value &&
|
||||
!impact.relatedForms.value.has(targetForm.value.id)
|
||||
)
|
||||
const showImpactDetails = computed(
|
||||
() =>
|
||||
sourceShotId.value &&
|
||||
(sourceMissing.value ||
|
||||
sourceCurrent.value ||
|
||||
sourceShot.value?.inconsistent ||
|
||||
targetUnlinked.value ||
|
||||
linkedReferences.value.length ||
|
||||
impact.references.loading.value ||
|
||||
impact.references.error.value)
|
||||
)
|
||||
const sourceLocation = computed(() =>
|
||||
sourceShot.value
|
||||
? {
|
||||
@@ -380,17 +409,53 @@ async function showStaleForms() {
|
||||
<NButton text size="small" @click="impact.query.refresh">重试检查</NButton></NAlert
|
||||
>
|
||||
<div class="form-image-filter-region">
|
||||
<div class="form-image-toolbar" :class="{ 'has-impact-picker': impactOptions.length || sourceShotId }">
|
||||
<NSelect
|
||||
<div
|
||||
class="form-image-toolbar"
|
||||
:class="{
|
||||
'has-impact-picker': impactOptions.length || sourceShotId,
|
||||
'has-impact-actions': sourceShotId || targetFormId
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-if="impactOptions.length || sourceShotId"
|
||||
:value="sourceShot?.shotId ?? null"
|
||||
:options="impactOptions"
|
||||
placeholder="选择过期镜头,查看关联素材"
|
||||
clearable
|
||||
aria-label="选择关联镜头"
|
||||
class="asset-impact-select"
|
||||
@update:value="selectImpactShot"
|
||||
/>
|
||||
class="asset-impact-picker"
|
||||
role="group"
|
||||
aria-label="镜头定位与导航"
|
||||
>
|
||||
<NSelect
|
||||
:value="sourceShot?.shotId ?? null"
|
||||
:options="impactOptions"
|
||||
placeholder="选择过期镜头,查看关联素材"
|
||||
clearable
|
||||
aria-label="选择关联镜头"
|
||||
class="asset-impact-select"
|
||||
@update:value="selectImpactShot"
|
||||
/>
|
||||
<RouterLink v-if="sourceLocation" :to="sourceLocation" custom v-slot="{ href, navigate }">
|
||||
<NButton
|
||||
tag="a"
|
||||
:href="href"
|
||||
@click="navigate"
|
||||
class="icon-button"
|
||||
:aria-label="`返回第 ${sourceShot?.episodeNo} 集 · 镜头 ${sourceShot?.shotNo}`"
|
||||
:title="`返回第 ${sourceShot?.episodeNo} 集 · 镜头 ${sourceShot?.shotNo}`"
|
||||
>
|
||||
<template #icon><ArrowLeft :size="16" /></template>
|
||||
<span class="sr-only"
|
||||
>返回第 {{ sourceShot?.episodeNo }} 集 · 镜头 {{ sourceShot?.shotNo }}</span
|
||||
>
|
||||
</NButton>
|
||||
</RouterLink>
|
||||
<NButton
|
||||
v-if="sourceShotId || targetFormId"
|
||||
class="icon-button"
|
||||
aria-label="查看全部素材"
|
||||
title="查看全部素材(清除定位与筛选)"
|
||||
@click="resetFilters"
|
||||
>
|
||||
<template #icon><LayoutGrid :size="16" /></template>
|
||||
</NButton>
|
||||
</div>
|
||||
<div class="form-image-filters" role="group" aria-label="形态图片筛选">
|
||||
<NInput
|
||||
placeholder="搜索主体、引用或形态"
|
||||
@@ -417,51 +482,22 @@ async function showStaleForms() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="sourceShotId" class="asset-impact-context mt-3" aria-label="镜头与素材定位">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<RouterLink v-if="sourceLocation" :to="sourceLocation" class="text-button"
|
||||
>返回第 {{ sourceShot?.episodeNo }} 集 · 镜头 {{ sourceShot?.shotNo }} →</RouterLink
|
||||
>
|
||||
<NButton v-if="targetFormId || sourceShotId" text size="small" @click="resetFilters"
|
||||
>查看全部素材</NButton
|
||||
>
|
||||
</div>
|
||||
<p
|
||||
v-if="sourceShotId && !sourceShot && !impact.query.loading.value && !impact.query.error.value"
|
||||
class="mt-3 text-danger"
|
||||
>
|
||||
来源镜头不存在或不属于当前项目,未读取其素材。
|
||||
</p>
|
||||
<p
|
||||
v-if="
|
||||
sourceShot &&
|
||||
!sourceShot.primaryKeyframeStale &&
|
||||
!impact.query.error.value &&
|
||||
!impact.query.loading.value
|
||||
"
|
||||
class="mt-3 text-xs"
|
||||
>
|
||||
<div v-if="showImpactDetails" class="asset-impact-context mt-3" aria-label="镜头与素材定位">
|
||||
<p v-if="sourceMissing" class="text-danger">来源镜头不存在或不属于当前项目,未读取其素材。</p>
|
||||
<p v-if="sourceCurrent" class="text-xs">
|
||||
当前检查:{{
|
||||
sourceShot.primaryKeyframeId ? '该镜头主首帧未被标记为过期' : '该镜头暂无主首帧'
|
||||
sourceShot?.primaryKeyframeId ? '该镜头主首帧未被标记为过期' : '该镜头暂无主首帧'
|
||||
}}。请以后端最新就绪状态为准。
|
||||
</p>
|
||||
<p v-if="sourceShot?.inconsistent" class="mt-3 text-xs text-danger">
|
||||
<p v-if="sourceShot?.inconsistent" class="text-xs text-danger">
|
||||
当前镜头:首帧检查未标记过期,但视频检查报告过期。请核对后端检查逻辑与身份母版,暂不建议重复生图。
|
||||
</p>
|
||||
<p
|
||||
v-if="
|
||||
targetForm &&
|
||||
impact.references.data.value &&
|
||||
!impact.references.error.value &&
|
||||
!impact.relatedForms.value.has(targetForm.id)
|
||||
"
|
||||
class="mt-3 text-xs text-danger"
|
||||
>
|
||||
<p v-if="targetUnlinked" class="text-xs text-danger">
|
||||
指定形态不在该镜头当前的有效引用列表中,关联可能已调整或主图缺失,请从下方关联素材重新核对。
|
||||
</p>
|
||||
<div
|
||||
v-if="linkedReferences.length && !impact.references.error.value"
|
||||
class="mt-3 flex flex-wrap items-center gap-3"
|
||||
class="flex flex-wrap items-center gap-3"
|
||||
>
|
||||
<span class="text-xs text-muted">本镜头当前关联素材:</span
|
||||
><RouterLink
|
||||
@@ -472,8 +508,8 @@ async function showStaleForms() {
|
||||
>{{ target.label }} →</RouterLink
|
||||
>
|
||||
</div>
|
||||
<p v-if="impact.references.loading.value" class="mt-2 text-xs text-muted">正在读取镜头关联素材…</p>
|
||||
<p v-if="impact.references.error.value" class="mt-2 text-xs text-danger">
|
||||
<p v-if="impact.references.loading.value" class="text-xs text-muted">正在读取镜头关联素材…</p>
|
||||
<p v-if="impact.references.error.value" class="text-xs text-danger">
|
||||
{{ impact.references.error.value }}
|
||||
<NButton text size="small" @click="impact.references.refresh">重试读取素材</NButton>
|
||||
</p>
|
||||
|
||||
@@ -231,7 +231,7 @@ describe('过期首帧到具体素材定位', () => {
|
||||
expect(fetcher.mock.calls.some(([url]) => String(url).endsWith('/references'))).toBe(false)
|
||||
// 镜头选择和图库筛选共享上方工具栏;未选择镜头时不留下空的关联说明面板。
|
||||
const toolbar = wrapper!.get('.form-image-toolbar')
|
||||
const picker = toolbar.get('.asset-impact-select').element
|
||||
const picker = toolbar.get('.asset-impact-picker').element
|
||||
expect(toolbar.classes()).toContain('has-impact-picker')
|
||||
expect(picker.nextElementSibling).toBe(toolbar.get('.form-image-filters').element)
|
||||
expect(toolbar.find('input[aria-label="搜索形态图片"]').exists()).toBe(true)
|
||||
@@ -248,6 +248,27 @@ describe('过期首帧到具体素材定位', () => {
|
||||
expect(wrapper!.text()).toContain('待处理首帧关联此素材')
|
||||
expect(fetcher.mock.calls.filter(([url]) => String(url).endsWith('/references'))).toHaveLength(1)
|
||||
})
|
||||
it('镜头导航与筛选共行,无关联说明时不留横条,查看全部素材清除定位和筛选', async () => {
|
||||
const { references, router, fetcher } = await gallery()
|
||||
references.references = []
|
||||
await router.push({ query: { sourceShotId: 'shot-target' } })
|
||||
await flushPromises()
|
||||
const picker = wrapper!.get('.asset-impact-picker')
|
||||
const back = picker.get('a[aria-label="返回第 2 集 · 镜头 1"]')
|
||||
expect(back.classes()).toContain('icon-button')
|
||||
expect(new URL(back.attributes('href')!, 'https://local.test').searchParams.get('shotId')).toBe('shot-target')
|
||||
expect(picker.get('[aria-label="查看全部素材"]').classes()).toContain('icon-button')
|
||||
expect(wrapper!.find('.asset-impact-context').exists()).toBe(false)
|
||||
await wrapper!.get('input[aria-label="搜索形态图片"]').setValue('不存在的素材')
|
||||
expect(wrapper!.findAll('.form-image-card')).toHaveLength(0)
|
||||
await picker.get('[aria-label="查看全部素材"]').trigger('click')
|
||||
await flushPromises()
|
||||
expect(router.currentRoute.value.query.sourceShotId).toBeUndefined()
|
||||
expect(wrapper!.findAll('.form-image-card')).toHaveLength(3)
|
||||
expect(wrapper!.find('.asset-impact-picker a').exists()).toBe(false)
|
||||
expect(fetcher.mock.calls.every(([, init]) => init?.method === 'GET')).toBe(true)
|
||||
})
|
||||
|
||||
it('精准定位卡片、清空旧类型筛选、提供可恢复原镜头的返回链接', async () => {
|
||||
const { router } = await gallery('?sourceShotId=shot-target&subjectFormId=form-special&subjectRef=%40CH0001')
|
||||
expect(wrapper!.findAll('.form-image-card')).toHaveLength(1)
|
||||
|
||||
Reference in New Issue
Block a user