feat: 同步精简接口并统一表单校验
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import AppForm from '../../components/ui/AppForm.vue'
|
||||
import { NFormItem } from 'naive-ui'
|
||||
import { integerRule } from '../../lib/form-rules'
|
||||
import WorkspacePage from '../../components/ui/WorkspacePage.vue'
|
||||
import ActionMenu from '../../components/ui/ActionMenu.vue'
|
||||
import DetailDisclosure from '../../components/ui/DetailDisclosure.vue'
|
||||
@@ -27,14 +30,13 @@ import {
|
||||
ArrowRight,
|
||||
LayoutGrid,
|
||||
Columns3,
|
||||
Settings2,
|
||||
AlertTriangle
|
||||
Settings2
|
||||
} from '@lucide/vue'
|
||||
import { AssetImage, EmptyState, StatusBadge } from '../../components/ui'
|
||||
import { downloadText } from '../../lib/format'
|
||||
import ConfirmAction from '../workflows/ConfirmAction.vue'
|
||||
import { useSubjectImages } from './useSubjectImages'
|
||||
import { coverImage, hasRunningImages, isPrimaryIdentityStale, primaryImage } from './model'
|
||||
import { coverImage, hasRunningImages, primaryImage } from './model'
|
||||
import type { GenerateFormImageInput, SubjectFormAsset } from './types'
|
||||
import GenerateImageDialog from './components/GenerateImageDialog.vue'
|
||||
import ImageGalleryDialog from './components/ImageGalleryDialog.vue'
|
||||
@@ -50,7 +52,6 @@ const router = useRouter()
|
||||
const {
|
||||
id,
|
||||
forms,
|
||||
staleForms,
|
||||
query,
|
||||
refreshProject,
|
||||
operation,
|
||||
@@ -60,15 +61,13 @@ const {
|
||||
limit,
|
||||
promptConcurrency,
|
||||
promptForce,
|
||||
concurrencyValid,
|
||||
generatePrompt,
|
||||
generatePrompts,
|
||||
blocked,
|
||||
running,
|
||||
batchValid,
|
||||
generate,
|
||||
generateProject,
|
||||
generateStale
|
||||
generateProject
|
||||
} = useSubjectImages()
|
||||
const sourceShotId = computed(() => queryText(route.query.sourceShotId))
|
||||
const targetFormId = computed(() => queryText(route.query.subjectFormId))
|
||||
@@ -149,12 +148,11 @@ const search = ref('')
|
||||
const layout = ref<GalleryLayout>('grid')
|
||||
const module = ref('all')
|
||||
/** 形态图片状态筛选;单选下拉避免移动端复选项占据两行。 */
|
||||
type ImageStatusFilter = 'all' | 'missing' | 'stale'
|
||||
type ImageStatusFilter = 'all' | 'missing'
|
||||
const statusFilter = ref<ImageStatusFilter>('all')
|
||||
const statusFilterOptions: Array<{ label: string; value: ImageStatusFilter }> = [
|
||||
{ label: '全部状态', value: 'all' },
|
||||
{ label: '缺少主图', value: 'missing' },
|
||||
{ label: '身份过期', value: 'stale' }
|
||||
{ label: '缺少主图', value: 'missing' }
|
||||
]
|
||||
/** 保存每张卡片的尺寸观察器,切换回网格时及时释放。 */
|
||||
const masonryCardObservers = new WeakMap<HTMLElement, ResizeObserver>()
|
||||
@@ -241,7 +239,6 @@ const filtered = computed(() =>
|
||||
(!targetFormId.value || form.id === targetFormId.value) &&
|
||||
(module.value === 'all' || form.subject.module === module.value) &&
|
||||
(statusFilter.value !== 'missing' || !primaryImage(form.images)) &&
|
||||
(statusFilter.value !== 'stale' || isPrimaryIdentityStale(form)) &&
|
||||
`${form.subject.name} ${form.subject.ref} ${form.name}`
|
||||
.toLowerCase()
|
||||
.includes(search.value.trim().toLowerCase())
|
||||
@@ -305,16 +302,8 @@ function selectImpactShot(shotId: string | null) {
|
||||
async function refreshAssets() {
|
||||
await Promise.all([refreshProject(), query.refresh(), impact.query.refresh(), impact.references.refresh()])
|
||||
}
|
||||
|
||||
/** 全局过期筛选不沿用深链接的单个素材限制。 */
|
||||
async function showStaleForms() {
|
||||
await router.replace({
|
||||
query: { ...route.query, subjectFormId: undefined, subjectRef: undefined, sourceShotId: undefined }
|
||||
})
|
||||
search.value = ''
|
||||
module.value = 'all'
|
||||
statusFilter.value = 'stale'
|
||||
}
|
||||
const formModel = computed(() => ({ concurrency: concurrency.value, limit: limit.value }))
|
||||
const rules = { concurrency: integerRule('批量并发'), limit: integerRule('本批上限', 1, true) }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -340,7 +329,7 @@ async function showStaleForms() {
|
||||
</div>
|
||||
<DetailDisclosure title="母版继承与过期处理" class="mt-4">
|
||||
人物、场景、道具形态图均可继承已锁定 Identity
|
||||
的母版,并记录本次引用来源。母版变化后可筛选过期图,重新生成候选并人工确认新的主参考图;不要直接沿用旧资产进入下游。
|
||||
的母版。母版变化后请核对形态图,按需生成候选并确认主图。当前图库不提供历史母版引用,无法自动判断形态图是否过期;下游状态以镜头就绪检查为准。
|
||||
<RouterLink :to="`/projects/${id}/subject-identity`" class="text-button ml-2"
|
||||
>管理主体身份 →</RouterLink
|
||||
></DetailDisclosure
|
||||
@@ -358,76 +347,61 @@ async function showStaleForms() {
|
||||
<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
|
||||
<AppForm :model="formModel" :rules="rules" :disabled="operation.pending" validate-on-change>
|
||||
<div class="form-controls mt-4 flex flex-wrap gap-4">
|
||||
<NFormItem
|
||||
class="w-28"
|
||||
path="concurrency"
|
||||
label="批量并发"
|
||||
:label-props="{ for: 'image-concurrency' }"
|
||||
>
|
||||
<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>
|
||||
</NFormItem>
|
||||
<NFormItem class="w-36" path="limit" label="本批上限(可选)"
|
||||
><NInputNumber
|
||||
:disabled="operation.pending"
|
||||
:value="typeof limit === 'number' ? limit : null"
|
||||
@update:value="limit = $event ?? ''"
|
||||
:min="1"
|
||||
:step="1"
|
||||
placeholder="不限制"
|
||||
:input-props="{ 'aria-label': '生图本批上限' }"
|
||||
/></NFormItem>
|
||||
<NCheckbox
|
||||
v-model:checked="force"
|
||||
: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>
|
||||
class="control-row-checkbox text-xs"
|
||||
>已有主图也新增候选图
|
||||
</NCheckbox>
|
||||
<ConfirmAction
|
||||
:label="force ? '为全项目新增候选图' : '补齐项目主参考图'"
|
||||
:disabled="blocked || !batchValid || !forms.length"
|
||||
acknowledgement
|
||||
:description="
|
||||
force
|
||||
? '对项目全部形态调用图片模型新增一张图片。已有主图不会被替换,生成后可在图片记录中手动选择主图。'
|
||||
: '仅对没有主图的形态调用图片模型生图,已有主图的形态跳过。操作面向整个项目,不受列表筛选影响。'
|
||||
"
|
||||
@confirm="generateProject"
|
||||
/>
|
||||
</div>
|
||||
</AppForm>
|
||||
<p class="mt-3 text-xs leading-6 text-muted">
|
||||
作用于整个项目,不受下方筛选影响。上限只限制本次真正生图数量,其余显示为“待后续处理”,不计入跳过。默认使用后端尺寸与正式提示词,不会启动视频生成。
|
||||
</p></NCollapseItem
|
||||
@@ -457,14 +431,6 @@ async function showStaleForms() {
|
||||
>
|
||||
部分形态生图失败。已生成的图片保留;先查看失败原因,再按形态重新生图。
|
||||
</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
|
||||
@@ -477,11 +443,6 @@ async function showStaleForms() {
|
||||
</section>
|
||||
</WorkspaceTools>
|
||||
<!-- 关联说明、筛选和图片共用正文滚动区,避免镜头切换时两块滚动区域高度不同步。 -->
|
||||
<NAlert v-if="staleForms.length" type="error" :show-icon="false" class="mt-3" title="形态主图需要更新">
|
||||
{{ staleForms.length }}
|
||||
个形态主图与当前已锁定身份母版不一致。请生成候选、验图并切换主图,再重建相关首帧。
|
||||
<NButton text size="small" @click="showStaleForms">筛选身份过期形态</NButton>
|
||||
</NAlert>
|
||||
<NAlert
|
||||
v-if="impact.stale.value.length"
|
||||
type="warning"
|
||||
@@ -730,7 +691,6 @@ async function showStaleForms() {
|
||||
:key="form.id"
|
||||
class="panel form-image-card"
|
||||
:class="{
|
||||
'ring-1 ring-danger': isPrimaryIdentityStale(form),
|
||||
'form-image-card-focused': targetFormId === form.id
|
||||
}"
|
||||
:data-form-id="form.id"
|
||||
@@ -755,9 +715,6 @@ async function showStaleForms() {
|
||||
</p>
|
||||
<div class="mt-3 flex flex-wrap items-center gap-2">
|
||||
<StatusBadge v-if="hasRunningImages(form.images)" status="generating" />
|
||||
<NTag v-if="isPrimaryIdentityStale(form)" size="small" :bordered="false"
|
||||
>身份母版已变更</NTag
|
||||
>
|
||||
<NTag v-else-if="primaryImage(form.images)" size="small" :bordered="false">主参考图</NTag>
|
||||
<NTag v-else-if="coverImage(form)" size="small" :bordered="false">候选图 · 尚未设主图</NTag>
|
||||
<span v-else-if="form.images[0]?.status === 'failed'" class="text-xs text-danger"
|
||||
@@ -765,15 +722,6 @@ async function showStaleForms() {
|
||||
>
|
||||
<span class="text-[10px] text-muted">{{ form.images.length }} 条记录</span>
|
||||
</div>
|
||||
<NAlert
|
||||
v-if="isPrimaryIdentityStale(form)"
|
||||
type="error"
|
||||
:show-icon="false"
|
||||
class="mt-3 text-xs leading-6"
|
||||
>
|
||||
当前主图未能匹配最新已锁定母版。请基于当前 Identity Anchor
|
||||
重新生成候选图,并在确认后设置为新的主参考图。
|
||||
</NAlert>
|
||||
<NAlert
|
||||
v-if="
|
||||
sourceShot?.primaryKeyframeStale &&
|
||||
@@ -789,9 +737,7 @@ async function showStaleForms() {
|
||||
{{
|
||||
sourceShot.inconsistent
|
||||
? '后端首帧与视频检查结果不一致,请先核对,不要据此重复生图。'
|
||||
: isPrimaryIdentityStale(form)
|
||||
? '请先更新该形态主图,再重建首帧。'
|
||||
: '关联不等于本素材失效;核对形态主图与身份母版,确认无误后只需重建首帧。'
|
||||
: '关联不等于本素材失效;核对形态主图与身份母版,确认无误后只需重建首帧。'
|
||||
}}
|
||||
<RouterLink v-if="sourceLocation" :to="sourceLocation" class="text-button mt-2"
|
||||
>返回该镜头处理 →</RouterLink
|
||||
@@ -818,14 +764,7 @@ async function showStaleForms() {
|
||||
"
|
||||
/>
|
||||
<NButton :disabled="blocked" @click="openGenerate(form)"
|
||||
><ImagePlus :size="14" />
|
||||
{{
|
||||
isPrimaryIdentityStale(form)
|
||||
? '基于当前母版重新生成'
|
||||
: coverImage(form)
|
||||
? '再生成一张'
|
||||
: '生成图片'
|
||||
}}</NButton
|
||||
><ImagePlus :size="14" /> {{ coverImage(form) ? '再生成一张' : '生成图片' }}</NButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user