fix: 合并图库显示与操作入口
移除顶部主图统计,将刷新图库和批量生图改为带悬浮说明的图标按钮,并移动到布局切换按钮之后。
This commit is contained in:
+29
-26
@@ -1,48 +1,51 @@
|
||||
# Design QA
|
||||
|
||||
- Source visual truth: `/workspace/scratch/6e52996114b8/upload/18c36dd0-af66-494f-9633-c0f6466a7cc9.png`
|
||||
- Source pixel dimensions: `2048 × 106`.
|
||||
- Intended implementation viewport: PC dark theme, approximately `2048px` CSS width.
|
||||
- Source visual truth:
|
||||
- `/workspace/scratch/6e52996114b8/upload/ff48911d-ddad-4f26-8607-08b588b13a47.png`
|
||||
- `/workspace/scratch/6e52996114b8/upload/bd89ef3b-1804-44e7-b00c-d376e735fd70.png`
|
||||
- Source pixel dimensions: `378 × 58` and `282 × 124`.
|
||||
- Intended implementation viewport: responsive dark theme, with the focused mobile filter region at approximately `393px` CSS width.
|
||||
- Implementation screenshot: unavailable.
|
||||
- CSS size and density normalization: unavailable because the implementation could not be opened in the cloud browser.
|
||||
- State: 形态图片页顶部工具栏,主图统计在左、图库操作在右。
|
||||
- State: 形态图片页默认筛选与网格布局。
|
||||
|
||||
## Full-view comparison evidence
|
||||
|
||||
The source crop was opened and inspected. The toolbar content sits inside a tall horizontal band, while the summary and actions are separated by nearly the full desktop width. This makes the content appear vertically loose and the center read as a large unused region.
|
||||
The first source crop shows a separate top row containing the main-image count, refresh action, and batch-image action. The second crop identifies the display-control row after the result count and grid/masonry choices as the requested destination.
|
||||
|
||||
The component now uses symmetric `5px` vertical padding and a shared `34px` minimum alignment height for the toolbar, summary, and actions. On PC widths the actions no longer use automatic left margin, so the controls follow the summary as one compact toolbar group. Mobile keeps the existing split alignment.
|
||||
The separate top row is now removed. The refresh and batch-image actions are icon-only buttons placed immediately after the grid/masonry group. Both keep accessible labels and use `NPopover` for visible hover explanations. The batch configuration drawer remains mounted without rendering its original trigger.
|
||||
|
||||
A local Sites preview started successfully, but the cloud browser rejected `terminal.local` with `net::ERR_BLOCKED_BY_CLIENT`. Therefore no browser-rendered implementation screenshot is available.
|
||||
|
||||
## Focused region comparison evidence
|
||||
|
||||
- Vertical alignment: summary and action groups share a `34px` alignment box with centered items.
|
||||
- Header density: top and bottom padding are both `5px`.
|
||||
- PC horizontal rhythm: automatic spacer is removed only above `760px`, preventing the wide empty center.
|
||||
- Mobile behavior: the existing `margin-left: auto` remains active at `760px` and below.
|
||||
- Removed content: the standalone `主图 x / y` summary is no longer rendered.
|
||||
- Action order: result count → grid → masonry → refresh → batch image.
|
||||
- Icon controls: refresh and batch image use the shared square `icon-button` sizing.
|
||||
- Explanations: both new icon controls have hover-triggered `NPopover` copy.
|
||||
- Drawer behavior: the external batch icon owns `aria-expanded` and opens the existing configuration drawer.
|
||||
|
||||
These are code-level checks only and do not replace a rendered focused-region comparison.
|
||||
|
||||
## Required fidelity surfaces
|
||||
|
||||
- Fonts and typography: unchanged; browser comparison is blocked.
|
||||
- Spacing and layout rhythm: header padding is symmetric and the PC toolbar is grouped continuously.
|
||||
- Colors and visual tokens: unchanged.
|
||||
- Fonts and typography: the removed labels no longer consume toolbar width; popover typography remains Naive UI-managed.
|
||||
- Spacing and layout rhythm: actions are consolidated into the existing display-control flex row.
|
||||
- Colors and visual tokens: existing button states and theme tokens are unchanged.
|
||||
- Image quality and asset fidelity: no image behavior changed.
|
||||
- Copy and content: unchanged.
|
||||
- Copy and content: `刷新图库` and `批量生图` move from button text into popover and accessible labels; receipt state remains visible in the batch popover.
|
||||
|
||||
## Findings
|
||||
|
||||
- [P1] Browser-rendered comparison unavailable
|
||||
- Location: 形态图片页顶部工具栏。
|
||||
- Location: 形态图片筛选区的显示与操作按钮。
|
||||
- Evidence: the required cloud browser returns `net::ERR_BLOCKED_BY_CLIENT` for the healthy local preview.
|
||||
- Impact: exact vertical centering, final toolbar width, responsive transition, and console state cannot be visually certified.
|
||||
- Fix: capture the page in an available browser at the same desktop width and compare the toolbar crop.
|
||||
- Impact: exact wrapping, popover position, hover behavior, drawer transition, and console state cannot be visually certified.
|
||||
- Fix: capture the page in an available browser at the requested responsive widths and compare the consolidated control row.
|
||||
|
||||
## Primary interactions tested
|
||||
|
||||
Automated style-contract tests protect the symmetric padding, shared alignment height, and PC-only removal of the automatic spacer. All 275 tests pass. Browser interactions were unavailable.
|
||||
Automated tests verify the new accessible refresh trigger, removal of the old heading row, batch drawer opening through the relocated trigger, and all existing refresh/batch workflows. All 275 tests pass. Browser hover interaction was unavailable.
|
||||
|
||||
## Console errors checked
|
||||
|
||||
@@ -50,16 +53,16 @@ Not checked because the page could not be opened in the cloud browser.
|
||||
|
||||
## Comparison history
|
||||
|
||||
- Iteration 1: identified asymmetric perceived vertical spacing and an oversized empty center caused by right-pushing the action group.
|
||||
- Iteration 2: normalized the toolbar to a compact `44px` total height and grouped desktop actions beside the summary. Full tests and production build pass; browser capture remains blocked.
|
||||
- Iteration 1: identified duplicated vertical chrome and text-heavy actions outside the filter controls.
|
||||
- Iteration 2: removed the standalone summary row, consolidated two icon-only actions after the layout controls, added popover explanations, and passed the full test/build suite. Browser capture remained blocked.
|
||||
|
||||
## Implementation checklist
|
||||
|
||||
- [x] Center summary and actions within the same toolbar height.
|
||||
- [x] Use equal top and bottom header padding.
|
||||
- [x] Remove the wide PC-only automatic spacer.
|
||||
- [x] Preserve the existing mobile split layout.
|
||||
- [x] Add style-contract regression assertions.
|
||||
- [ ] Capture and compare the rendered desktop state in the cloud browser.
|
||||
- [x] Remove the standalone main-image summary.
|
||||
- [x] Convert refresh and batch image actions to icon-only buttons.
|
||||
- [x] Move both actions after the layout switch.
|
||||
- [x] Add `NPopover` explanations and accessible labels.
|
||||
- [x] Preserve batch drawer and refresh behavior.
|
||||
- [ ] Capture and compare the rendered responsive state in the cloud browser.
|
||||
|
||||
final result: blocked
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
import { NButton, NDrawer, NDrawerContent } from 'naive-ui'
|
||||
import { Settings2, X } from '@lucide/vue'
|
||||
|
||||
/** 所有业务抽屉挂到 body,以完整视口为边界;开关面板不影响草稿、页面查询和后台任务。 */
|
||||
withDefaults(defineProps<{ title: string; label?: string; hasReceipt?: boolean }>(), { label: '操作与回执' })
|
||||
/** 所有业务抽屉挂到 body,以完整视口为边界;triggerless 允许业务页把入口放进自己的工具栏。 */
|
||||
withDefaults(defineProps<{ title: string; label?: string; hasReceipt?: boolean; triggerless?: boolean }>(), {
|
||||
label: '操作与回执',
|
||||
triggerless: false
|
||||
})
|
||||
const open = defineModel<boolean>('open', { default: false })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NButton
|
||||
v-if="!triggerless"
|
||||
class="workspace-tools-trigger"
|
||||
data-workspace-tools
|
||||
:aria-label="label"
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
NCollapseItem,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NPopover,
|
||||
NScrollbar,
|
||||
NSelect,
|
||||
NTag
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
ArrowRight,
|
||||
LayoutGrid,
|
||||
Columns3,
|
||||
Settings2,
|
||||
AlertTriangle
|
||||
} from '@lucide/vue'
|
||||
import { AssetImage, EmptyState, StatusBadge } from '../../components/ui'
|
||||
@@ -308,179 +310,163 @@ async function showStaleForms() {
|
||||
|
||||
<template>
|
||||
<WorkspacePage compact class="gallery-workspace-page"
|
||||
><template #header>
|
||||
<!-- 图库统计和固定操作使用与其他工作台一致的紧凑头部。 -->
|
||||
<div class="workspace-toolbar">
|
||||
<span class="toolbar-summary">主图 {{ completeCount }} / {{ forms.length }}</span>
|
||||
<NButton v-if="staleForms.length" text type="error" size="small" @click="toolsOpen = true"
|
||||
>{{ staleForms.length }} 个形态主图过期</NButton
|
||||
>
|
||||
<div class="toolbar-actions">
|
||||
<NButton
|
||||
:disabled="query.loading.value || impact.query.loading.value"
|
||||
@click="refreshAssets"
|
||||
text
|
||||
size="small"
|
||||
><RefreshCw :size="13" :class="{ 'animate-spin': query.loading.value }" />刷新图库 </NButton
|
||||
><WorkspaceTools
|
||||
v-model:open="toolsOpen"
|
||||
title="形态生图配置与回执"
|
||||
label="批量生图"
|
||||
:has-receipt="!!session.receipt || !!session.promptReceipt"
|
||||
><div class="flex flex-wrap items-end justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold">形态图片</h2>
|
||||
<p class="mt-2 text-sm text-muted">
|
||||
为人物、场景和道具准备造型参考图。已有图片直接从数据库读取。
|
||||
</p>
|
||||
</div>
|
||||
<RouterLink :to="`/projects/${id}/storyboard`" class="text-button"
|
||||
>去检查分镜参考图<ArrowRight :size="14"
|
||||
/></RouterLink>
|
||||
</div>
|
||||
<DetailDisclosure title="母版继承与过期处理" class="mt-4">
|
||||
人物、场景、道具形态图均可继承已锁定 Identity
|
||||
的母版,并记录本次引用来源。母版变化后可筛选过期图,重新生成候选并人工确认新的主参考图;不要直接沿用旧资产进入下游。
|
||||
<RouterLink :to="`/projects/${id}/subject-identity`" class="text-button ml-2"
|
||||
>管理主体身份 →</RouterLink
|
||||
></DetailDisclosure
|
||||
>
|
||||
<FormPromptBatch
|
||||
:forms="forms"
|
||||
:disabled="blocked"
|
||||
:pending="operation.pending"
|
||||
:receipt="session.promptReceipt"
|
||||
v-model:concurrency="promptConcurrency"
|
||||
v-model:force="promptForce"
|
||||
@generate="generatePrompts"
|
||||
/>
|
||||
<div class="panel mt-5 p-5">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<p class="text-sm">
|
||||
<strong>{{ completeCount }}</strong> / {{ forms.length }} 个形态已有主图
|
||||
<span v-if="staleForms.length" class="ml-3 text-xs text-danger">
|
||||
{{ staleForms.length }} 个形态主图已过期
|
||||
</span>
|
||||
<span class="ml-3 text-xs text-muted">图片模型 · 后端配置</span>
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-2 text-[11px] leading-6 text-muted">
|
||||
图库不自动刷新,可点击“刷新图库”读取最新记录。主图供分镜引用,候选图和历史失败记录均保留;过期主图不会自动删除或替换。
|
||||
</p>
|
||||
<NAlert v-if="staleForms.length" type="error" :show-icon="false" class="mt-4"
|
||||
><div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<span class="flex items-center gap-2 text-xs">
|
||||
<AlertTriangle :size="14" />检测到 {{ staleForms.length }} 个形态 仍在使用旧
|
||||
Identity Anchor。
|
||||
</span>
|
||||
<ConfirmAction
|
||||
label="重新生成过期形态"
|
||||
:disabled="blocked || !concurrencyValid || !staleForms.length"
|
||||
acknowledgement
|
||||
description="只为身份母版已经变化的形态调用 图片模型,各新增一张基于当前已锁定母版的候选图;不会自动替换旧主图,生成后请人工确认并设置新的主参考图。"
|
||||
@confirm="generateStale"
|
||||
/></div
|
||||
></NAlert>
|
||||
<NCollapse class="mt-4 pt-4"
|
||||
><NCollapseItem name="details"
|
||||
><template #header>项目批量生图</template>
|
||||
<div class="mt-4 flex flex-wrap items-end gap-4">
|
||||
<label class="w-28">
|
||||
<span class="field-label">批量并发</span>
|
||||
<NInputNumber
|
||||
:disabled="operation.pending"
|
||||
:input-props="{ id: 'image-concurrency' }"
|
||||
:value="typeof concurrency === 'number' ? concurrency : null"
|
||||
@update:value="concurrency = $event ?? 0"
|
||||
:min="1"
|
||||
:step="1"
|
||||
></NInputNumber>
|
||||
</label>
|
||||
<label class="w-36"
|
||||
><span class="field-label">本批上限(可选)</span
|
||||
><NInputNumber
|
||||
:disabled="operation.pending"
|
||||
:value="typeof limit === 'number' ? limit : null"
|
||||
@update:value="limit = $event ?? ''"
|
||||
:min="1"
|
||||
:step="1"
|
||||
placeholder="不限制"
|
||||
:input-props="{ 'aria-label': '生图本批上限' }"
|
||||
/></label>
|
||||
<NCheckbox
|
||||
v-model:checked="force"
|
||||
:disabled="operation.pending"
|
||||
class="control-row-checkbox text-xs"
|
||||
>已有主图也新增候选图
|
||||
</NCheckbox>
|
||||
<ConfirmAction
|
||||
:label="force ? '为全项目新增候选图' : '补齐项目主参考图'"
|
||||
:disabled="blocked || !batchValid || !forms.length"
|
||||
acknowledgement
|
||||
:description="
|
||||
force
|
||||
? '对项目全部形态调用图片模型新增一张图片。已有主图不会被替换,生成后可在图片记录中手动选择主图。'
|
||||
: '仅对没有主图的形态调用图片模型生图,已有主图的形态跳过。操作面向整个项目,不受列表筛选影响。'
|
||||
"
|
||||
@confirm="generateProject"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="!batchValid" class="mt-2 text-xs text-danger">
|
||||
并发和填写的数量上限必须是正整数。
|
||||
</p>
|
||||
<p class="mt-3 text-xs leading-6 text-muted">
|
||||
作用于整个项目,不受下方筛选影响。上限只限制本次真正生图数量,其余显示为“待后续处理”,不计入跳过。默认使用后端尺寸与正式提示词,不会启动视频生成。
|
||||
</p></NCollapseItem
|
||||
></NCollapse
|
||||
>
|
||||
</div>
|
||||
<section v-if="session.receipt" class="panel mt-4 p-5" aria-label="生图批量回执">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<h3 class="text-sm font-medium">{{ session.receipt.title }} · 本次回执</h3>
|
||||
<NButton @click="exportReceipt" text size="small"
|
||||
><Download :size="13" />导出诊断</NButton
|
||||
>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-muted">
|
||||
共 {{ session.receipt.result.total }} 个形态 · 本次目标
|
||||
{{ session.receipt.result.targetCount }} · 生成 {{ session.receipt.result.generated }} ·
|
||||
跳过 {{ session.receipt.result.skipped }} · 失败
|
||||
{{ session.receipt.result.failed }}
|
||||
<span v-if="session.receipt.result.remaining !== undefined">
|
||||
· 待后续处理 {{ session.receipt.result.remaining }}</span
|
||||
>
|
||||
</p>
|
||||
<NAlert
|
||||
v-if="session.receipt.result.failed"
|
||||
role="alert"
|
||||
type="error"
|
||||
:show-icon="false"
|
||||
class="mt-3"
|
||||
>
|
||||
部分形态生图失败。已生成的图片保留;先查看失败原因,再按形态重新生图。
|
||||
</NAlert>
|
||||
<NAlert
|
||||
v-else-if="session.receipt.title === '刷新过期形态图'"
|
||||
type="info"
|
||||
:show-icon="false"
|
||||
class="mt-3 text-xs"
|
||||
>
|
||||
新图当前仍是候选图。请进入对应形态的“查看图片与记录”,确认人物身份和造型后再设为主参考图;旧主图在此之前仍保持过期状态。
|
||||
</NAlert>
|
||||
<ul class="mt-3 space-y-2 text-xs text-danger">
|
||||
<li v-for="failure in session.receipt.result.failures" :key="failure.subjectFormId">
|
||||
<code>{{ failure.subjectFormId }}</code
|
||||
>:{{ failure.error }}
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mt-3 text-[11px] text-muted">
|
||||
回执仅保留于当前浏览器会话;图片以刷新后的数据库记录为准。
|
||||
</p>
|
||||
</section>
|
||||
</WorkspaceTools>
|
||||
</div>
|
||||
</div></template
|
||||
><template #default>
|
||||
<!-- 抽屉只保留内容,入口跟随图库显示控制移动到筛选区。 -->
|
||||
<WorkspaceTools
|
||||
triggerless
|
||||
v-model:open="toolsOpen"
|
||||
title="形态生图配置与回执"
|
||||
label="批量生图"
|
||||
:has-receipt="!!session.receipt || !!session.promptReceipt"
|
||||
><div class="flex flex-wrap items-end justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold">形态图片</h2>
|
||||
<p class="mt-2 text-sm text-muted">
|
||||
为人物、场景和道具准备造型参考图。已有图片直接从数据库读取。
|
||||
</p>
|
||||
</div>
|
||||
<RouterLink :to="`/projects/${id}/storyboard`" class="text-button"
|
||||
>去检查分镜参考图<ArrowRight :size="14"
|
||||
/></RouterLink>
|
||||
</div>
|
||||
<DetailDisclosure title="母版继承与过期处理" class="mt-4">
|
||||
人物、场景、道具形态图均可继承已锁定 Identity
|
||||
的母版,并记录本次引用来源。母版变化后可筛选过期图,重新生成候选并人工确认新的主参考图;不要直接沿用旧资产进入下游。
|
||||
<RouterLink :to="`/projects/${id}/subject-identity`" class="text-button ml-2"
|
||||
>管理主体身份 →</RouterLink
|
||||
></DetailDisclosure
|
||||
>
|
||||
<FormPromptBatch
|
||||
:forms="forms"
|
||||
:disabled="blocked"
|
||||
:pending="operation.pending"
|
||||
:receipt="session.promptReceipt"
|
||||
v-model:concurrency="promptConcurrency"
|
||||
v-model:force="promptForce"
|
||||
@generate="generatePrompts"
|
||||
/>
|
||||
<div class="panel mt-5 p-5">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<p class="text-sm">
|
||||
<strong>{{ completeCount }}</strong> / {{ forms.length }} 个形态已有主图
|
||||
<span v-if="staleForms.length" class="ml-3 text-xs text-danger">
|
||||
{{ staleForms.length }} 个形态主图已过期
|
||||
</span>
|
||||
<span class="ml-3 text-xs text-muted">图片模型 · 后端配置</span>
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-2 text-[11px] leading-6 text-muted">
|
||||
图库不自动刷新,可点击“刷新图库”读取最新记录。主图供分镜引用,候选图和历史失败记录均保留;过期主图不会自动删除或替换。
|
||||
</p>
|
||||
<NAlert v-if="staleForms.length" type="error" :show-icon="false" class="mt-4"
|
||||
><div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<span class="flex items-center gap-2 text-xs">
|
||||
<AlertTriangle :size="14" />检测到 {{ staleForms.length }} 个形态 仍在使用旧 Identity
|
||||
Anchor。
|
||||
</span>
|
||||
<ConfirmAction
|
||||
label="重新生成过期形态"
|
||||
:disabled="blocked || !concurrencyValid || !staleForms.length"
|
||||
acknowledgement
|
||||
description="只为身份母版已经变化的形态调用 图片模型,各新增一张基于当前已锁定母版的候选图;不会自动替换旧主图,生成后请人工确认并设置新的主参考图。"
|
||||
@confirm="generateStale"
|
||||
/></div
|
||||
></NAlert>
|
||||
<NCollapse class="mt-4 pt-4"
|
||||
><NCollapseItem name="details"
|
||||
><template #header>项目批量生图</template>
|
||||
<div class="mt-4 flex flex-wrap items-end gap-4">
|
||||
<label class="w-28">
|
||||
<span class="field-label">批量并发</span>
|
||||
<NInputNumber
|
||||
:disabled="operation.pending"
|
||||
:input-props="{ id: 'image-concurrency' }"
|
||||
:value="typeof concurrency === 'number' ? concurrency : null"
|
||||
@update:value="concurrency = $event ?? 0"
|
||||
:min="1"
|
||||
:step="1"
|
||||
></NInputNumber>
|
||||
</label>
|
||||
<label class="w-36"
|
||||
><span class="field-label">本批上限(可选)</span
|
||||
><NInputNumber
|
||||
:disabled="operation.pending"
|
||||
:value="typeof limit === 'number' ? limit : null"
|
||||
@update:value="limit = $event ?? ''"
|
||||
:min="1"
|
||||
:step="1"
|
||||
placeholder="不限制"
|
||||
:input-props="{ 'aria-label': '生图本批上限' }"
|
||||
/></label>
|
||||
<NCheckbox
|
||||
v-model:checked="force"
|
||||
:disabled="operation.pending"
|
||||
class="control-row-checkbox text-xs"
|
||||
>已有主图也新增候选图
|
||||
</NCheckbox>
|
||||
<ConfirmAction
|
||||
:label="force ? '为全项目新增候选图' : '补齐项目主参考图'"
|
||||
:disabled="blocked || !batchValid || !forms.length"
|
||||
acknowledgement
|
||||
:description="
|
||||
force
|
||||
? '对项目全部形态调用图片模型新增一张图片。已有主图不会被替换,生成后可在图片记录中手动选择主图。'
|
||||
: '仅对没有主图的形态调用图片模型生图,已有主图的形态跳过。操作面向整个项目,不受列表筛选影响。'
|
||||
"
|
||||
@confirm="generateProject"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="!batchValid" class="mt-2 text-xs text-danger">
|
||||
并发和填写的数量上限必须是正整数。
|
||||
</p>
|
||||
<p class="mt-3 text-xs leading-6 text-muted">
|
||||
作用于整个项目,不受下方筛选影响。上限只限制本次真正生图数量,其余显示为“待后续处理”,不计入跳过。默认使用后端尺寸与正式提示词,不会启动视频生成。
|
||||
</p></NCollapseItem
|
||||
></NCollapse
|
||||
>
|
||||
</div>
|
||||
<section v-if="session.receipt" class="panel mt-4 p-5" aria-label="生图批量回执">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<h3 class="text-sm font-medium">{{ session.receipt.title }} · 本次回执</h3>
|
||||
<NButton @click="exportReceipt" text size="small"><Download :size="13" />导出诊断</NButton>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-muted">
|
||||
共 {{ session.receipt.result.total }} 个形态 · 本次目标
|
||||
{{ session.receipt.result.targetCount }} · 生成 {{ session.receipt.result.generated }} · 跳过
|
||||
{{ session.receipt.result.skipped }} · 失败
|
||||
{{ session.receipt.result.failed }}
|
||||
<span v-if="session.receipt.result.remaining !== undefined">
|
||||
· 待后续处理 {{ session.receipt.result.remaining }}</span
|
||||
>
|
||||
</p>
|
||||
<NAlert
|
||||
v-if="session.receipt.result.failed"
|
||||
role="alert"
|
||||
type="error"
|
||||
:show-icon="false"
|
||||
class="mt-3"
|
||||
>
|
||||
部分形态生图失败。已生成的图片保留;先查看失败原因,再按形态重新生图。
|
||||
</NAlert>
|
||||
<NAlert
|
||||
v-else-if="session.receipt.title === '刷新过期形态图'"
|
||||
type="info"
|
||||
:show-icon="false"
|
||||
class="mt-3 text-xs"
|
||||
>
|
||||
新图当前仍是候选图。请进入对应形态的“查看图片与记录”,确认人物身份和造型后再设为主参考图;旧主图在此之前仍保持过期状态。
|
||||
</NAlert>
|
||||
<ul class="mt-3 space-y-2 text-xs text-danger">
|
||||
<li v-for="failure in session.receipt.result.failures" :key="failure.subjectFormId">
|
||||
<code>{{ failure.subjectFormId }}</code
|
||||
>:{{ failure.error }}
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mt-3 text-[11px] text-muted">
|
||||
回执仅保留于当前浏览器会话;图片以刷新后的数据库记录为准。
|
||||
</p>
|
||||
</section>
|
||||
</WorkspaceTools>
|
||||
<!-- 关联说明、筛选和图片共用正文滚动区,避免镜头切换时两块滚动区域高度不同步。 -->
|
||||
<NAlert v-if="staleForms.length" type="error" :show-icon="false" class="mt-3" title="形态主图需要更新">
|
||||
{{ staleForms.length }}
|
||||
@@ -608,6 +594,42 @@ async function showStaleForms() {
|
||||
<template #icon><Columns3 :size="16" /></template>
|
||||
</NButton>
|
||||
</div>
|
||||
<!-- 高频图库操作与显示方式连续排列;纯图标按钮通过 Popover 解释用途。 -->
|
||||
<NPopover trigger="hover" placement="top">
|
||||
<template #trigger>
|
||||
<NButton
|
||||
class="icon-button"
|
||||
size="small"
|
||||
:disabled="query.loading.value || impact.query.loading.value"
|
||||
aria-label="刷新图库"
|
||||
@click="refreshAssets"
|
||||
>
|
||||
<template #icon>
|
||||
<RefreshCw
|
||||
:size="16"
|
||||
:class="{ 'animate-spin': query.loading.value }"
|
||||
/>
|
||||
</template>
|
||||
</NButton>
|
||||
</template>
|
||||
刷新图库
|
||||
</NPopover>
|
||||
<NPopover trigger="hover" placement="top">
|
||||
<template #trigger>
|
||||
<NButton
|
||||
class="icon-button"
|
||||
size="small"
|
||||
data-workspace-tools
|
||||
aria-label="批量生图"
|
||||
aria-haspopup="dialog"
|
||||
:aria-expanded="toolsOpen"
|
||||
@click="toolsOpen = true"
|
||||
>
|
||||
<template #icon><Settings2 :size="16" /></template>
|
||||
</NButton>
|
||||
</template>
|
||||
{{ session.receipt || session.promptReceipt ? '批量生图(有回执)' : '批量生图' }}
|
||||
</NPopover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -830,20 +852,6 @@ async function showStaleForms() {
|
||||
.gallery-workspace-page .workspace-scroll-content {
|
||||
@apply pt-2;
|
||||
}
|
||||
/* PC 端工具栏保持紧凑并从左向右连续排列;统一最小高度让统计和按钮上下居中。 */
|
||||
.gallery-workspace-page .workspace-heading {
|
||||
@apply py-[5px];
|
||||
}
|
||||
.gallery-workspace-page .workspace-toolbar,
|
||||
.gallery-workspace-page .toolbar-summary,
|
||||
.gallery-workspace-page .toolbar-actions {
|
||||
@apply min-h-[34px] items-center;
|
||||
}
|
||||
@media (min-width: 761px) {
|
||||
.gallery-workspace-page .toolbar-actions {
|
||||
@apply ml-0;
|
||||
}
|
||||
}
|
||||
.gallery-workspace-page .gallery-sticky-controls {
|
||||
@apply sticky top-0 z-10 bg-(--app-body) mt-2.5 mb-0;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ describe('全站平面主题', () => {
|
||||
)
|
||||
expect(css).toMatch(/\.asset-impact-links-scroll\.n-scrollbar\s*\{[^}]*height:\s*auto;[^}]*max-width:\s*100%;/)
|
||||
expect(readFileSync('src/features/subject-images/SubjectImagesPage.vue', 'utf8')).toMatch(
|
||||
/<WorkspacePage\b[^>]*>\s*<template #header>/
|
||||
/<WorkspacePage\b[^>]*>\s*<template #default>/
|
||||
)
|
||||
})
|
||||
|
||||
@@ -183,13 +183,12 @@ describe('全站平面主题', () => {
|
||||
it('图库顶部统一镜头选择和筛选的背景与垂直对齐,窄屏整组换行', () => {
|
||||
// DOM 环境不计算几何;保护容器宽度断点和居中契约,真实坐标仍需浏览器验收。
|
||||
const css = readAllStyles()
|
||||
expect(css).toMatch(/\.gallery-workspace-page \.workspace-heading\s*\{[^}]*padding-block:\s*5px;/)
|
||||
expect(css).toMatch(
|
||||
/\.gallery-workspace-page \.workspace-toolbar,\s*\.gallery-workspace-page \.toolbar-summary,\s*\.gallery-workspace-page \.toolbar-actions\s*\{(?=[^}]*align-items:\s*center;)(?=[^}]*min-height:\s*34px;)/
|
||||
)
|
||||
expect(css).toMatch(
|
||||
/@media \(min-width: 761px\)\s*\{[^}]*\.gallery-workspace-page \.toolbar-actions\s*\{[^}]*margin-left:\s*0;/
|
||||
)
|
||||
const source = readFileSync('src/features/subject-images/SubjectImagesPage.vue', 'utf8')
|
||||
expect(source).not.toContain('class="toolbar-summary"')
|
||||
expect(source).toMatch(/<WorkspaceTools\s+triggerless/)
|
||||
expect(source.match(/<NPopover trigger="hover" placement="top">/g)).toHaveLength(2)
|
||||
expect(source).toMatch(/aria-label="刷新图库"/)
|
||||
expect(source).toMatch(/data-workspace-tools\s+aria-label="批量生图"/)
|
||||
expect(css).toMatch(
|
||||
/\.form-image-filter-region\s*\{[^}]*padding:\s*12px;[^}]*--app-field:\s*var\(--app-surface\);[^}]*--app-field-hover:\s*var\(--app-surface\);[^}]*background:\s*color-mix\(in srgb, var\(--app-subtle\) 96%, white\);/
|
||||
)
|
||||
|
||||
@@ -174,7 +174,7 @@ describe('过期首帧到具体素材定位', () => {
|
||||
await flushPromises()
|
||||
expect(fetcher).toHaveBeenCalledTimes(count)
|
||||
expect(wrapper!.get('.form-image-grid').element).toBe(grid)
|
||||
const refresh = wrapper!.findAll('button').find(button => button.text().includes('刷新图库'))!
|
||||
const refresh = wrapper!.get('[aria-label="刷新图库"]')
|
||||
await refresh.trigger('click')
|
||||
await flushPromises()
|
||||
expect(fetcher.mock.calls.length).toBeGreaterThan(count)
|
||||
@@ -185,7 +185,7 @@ describe('过期首帧到具体素材定位', () => {
|
||||
|
||||
it('切换镜头时筛选、关联说明和图片共用稳定纵向容器,长关联列表单独横向滚动', async () => {
|
||||
const { keyframes, videos, references, fetcher } = await gallery('?sourceShotId=shot-target')
|
||||
expect(wrapper!.find('.workspace-heading-scroll').exists()).toBe(true)
|
||||
expect(wrapper!.find('.workspace-heading-scroll').exists()).toBe(false)
|
||||
const scroll = wrapper!.get<HTMLElement>('.workspace-scroll > .n-scrollbar-container').element
|
||||
const content = wrapper!.get('.workspace-scroll-content').element
|
||||
const sticky = wrapper!.get('.gallery-sticky-controls').element
|
||||
@@ -207,10 +207,7 @@ describe('过期首帧到具体素材定位', () => {
|
||||
})
|
||||
: original(url, init)
|
||||
)
|
||||
await wrapper!
|
||||
.findAll('button')
|
||||
.find(button => button.text() === '刷新图库')!
|
||||
.trigger('click')
|
||||
await wrapper!.get('[aria-label="刷新图库"]').trigger('click')
|
||||
await flushPromises()
|
||||
wrapper!
|
||||
.findAllComponents(NSelect)
|
||||
@@ -414,7 +411,7 @@ describe('过期首帧到具体素材定位', () => {
|
||||
it('身份主图真的过期时显示独立顶部警告,并可从定位模式筛选所有过期素材', async () => {
|
||||
const { first, router } = await gallery('?subjectFormId=form-other')
|
||||
first.subject.identity = { id: 'identity-1', isLocked: true, images: [{ id: 'anchor-new' }] }
|
||||
const refresh = wrapper!.findAll('button').find(button => button.text() === '刷新图库')!
|
||||
const refresh = wrapper!.get('[aria-label="刷新图库"]')
|
||||
await refresh.trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper!.text()).toContain('形态主图需要更新')
|
||||
@@ -432,10 +429,7 @@ describe('过期首帧到具体素材定位', () => {
|
||||
it('仅视频接口报告过期时也显示警告,并明确两种检查不一致', async () => {
|
||||
const { keyframes } = await gallery('?sourceShotId=shot-target&subjectFormId=form-special')
|
||||
keyframes.items[0]!.primaryKeyframeStale = false
|
||||
await wrapper!
|
||||
.findAll('button')
|
||||
.find(button => button.text() === '刷新图库')!
|
||||
.trigger('click')
|
||||
await wrapper!.get('[aria-label="刷新图库"]').trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper!.get('[data-keyframe-impact]').text()).toContain('检查结果不一致')
|
||||
expect(wrapper!.get('.form-image-card').text()).toContain('不要据此重复生图')
|
||||
@@ -444,10 +438,7 @@ describe('过期首帧到具体素材定位', () => {
|
||||
const { keyframes, videos } = await gallery('?sourceShotId=shot-target&subjectFormId=form-special')
|
||||
keyframes.items[0]!.primaryKeyframeStale = false
|
||||
videos.items[0]!.issues = []
|
||||
await wrapper!
|
||||
.findAll('button')
|
||||
.find(button => button.text() === '刷新图库')!
|
||||
.trigger('click')
|
||||
await wrapper!.get('[aria-label="刷新图库"]').trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper!.find('[data-keyframe-impact]').exists()).toBe(false)
|
||||
expect(wrapper!.text()).toContain('未被标记为过期')
|
||||
@@ -467,10 +458,7 @@ describe('过期首帧到具体素材定位', () => {
|
||||
? Promise.resolve(new Response('{}', { status: 500 }))
|
||||
: original(url, init)
|
||||
)
|
||||
await wrapper!
|
||||
.findAll('button')
|
||||
.find(button => button.text() === '刷新图库')!
|
||||
.trigger('click')
|
||||
await wrapper!.get('[aria-label="刷新图库"]').trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper!.text()).toContain('暂时无法确认有无过期首帧')
|
||||
expect(wrapper!.findAll('.form-image-card')).toHaveLength(3)
|
||||
|
||||
Reference in New Issue
Block a user