fix: 使用 NScrollbar 统一内容区滚动条
This commit is contained in:
@@ -1,6 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import WorkspacePage from '../../components/ui/WorkspacePage.vue'
|
||||
import { NAlert, NButton, NCheckbox, NCollapse, NCollapseItem, NInput, NInputNumber, NSelect, NTag } from 'naive-ui'
|
||||
import {
|
||||
NScrollbar,
|
||||
NAlert,
|
||||
NButton,
|
||||
NCheckbox,
|
||||
NCollapse,
|
||||
NCollapseItem,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NSelect,
|
||||
NTag
|
||||
} from 'naive-ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { UserCheck } from '@lucide/vue'
|
||||
@@ -141,187 +152,188 @@ watch(
|
||||
>
|
||||
</div></template
|
||||
>
|
||||
<NAlert
|
||||
<NScrollbar
|
||||
class="workspace-feedback"
|
||||
v-if="catalog.error.value || styleQuery.error.value || castingQuery.error.value"
|
||||
role="alert"
|
||||
type="error"
|
||||
:show-icon="false"
|
||||
class="mt-4"
|
||||
>{{ catalog.error.value || styleQuery.error.value || castingQuery.error.value }}
|
||||
当前相关操作已暂停,请刷新核对后端。
|
||||
</NAlert>
|
||||
<NCollapse v-model:expanded-names="overview" class="workspace-overview"
|
||||
><NCollapseItem name="casting" title="角色选角总览与批量操作"
|
||||
><div class="overview-content">
|
||||
<NAlert type="info" :show-icon="false" class="mt-5 text-xs">
|
||||
Character 现在需要依次完成身份文本、选角候选、确认演员并锁定
|
||||
Identity,之后首帧才能就绪。未明确人物背景时,AI
|
||||
默认以中国人物作为选角基线;项目和角色的明确事实始终优先。场景和道具仍使用普通身份母版流程。
|
||||
</NAlert>
|
||||
<NAlert v-if="styleQuery.data.value && !hasStyle" type="info" :show-icon="false" class="mt-4">
|
||||
先创建项目视觉风格,才能 AI 生成身份或身份图片。<RouterLink
|
||||
:to="`/projects/${projectId}/visual-style`"
|
||||
class="text-button ml-2"
|
||||
>设置视觉风格 →</RouterLink
|
||||
></NAlert
|
||||
>
|
||||
<section v-if="casting" class="panel my-5 p-5" aria-label="角色选角流程">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<span class="production-step"><UserCheck :size="17" /></span>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold">角色选角</h3>
|
||||
<p class="mt-2 text-xs leading-6 text-muted">
|
||||
只处理 Character。每张候选都从 Identity 独立生成,不复用旧
|
||||
Anchor;确认选择时才会同时切换 Anchor 并锁定 Identity。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="font-mono text-xs text-muted"
|
||||
>{{ casting.ready }} / {{ casting.total }} 已完成</span
|
||||
>
|
||||
</div>
|
||||
<dl class="casting-stats mt-4">
|
||||
<div>
|
||||
<dt>缺身份文本</dt>
|
||||
<dd>{{ casting.missingIdentity }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>缺候选图</dt>
|
||||
<dd>{{ casting.missingAnchor }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>待确认演员</dt>
|
||||
<dd>{{ casting.candidatePending }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>母版未锁定</dt>
|
||||
<dd>{{ casting.unlocked }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>选角完成</dt>
|
||||
<dd>{{ casting.ready }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="mt-5 flex flex-wrap items-end gap-4 border-t border-line pt-4">
|
||||
<label class="w-28">
|
||||
<span class="field-label">并发数</span>
|
||||
<NInputNumber
|
||||
:disabled="operation.pending"
|
||||
:value="typeof concurrency === 'number' ? concurrency : null"
|
||||
@update:value="concurrency = $event ?? 0"
|
||||
:min="1"
|
||||
:step="1"
|
||||
></NInputNumber>
|
||||
</label>
|
||||
<label class="w-28">
|
||||
<span class="field-label">本批上限</span>
|
||||
<NInputNumber
|
||||
:disabled="operation.pending"
|
||||
:value="typeof candidateLimit === 'number' ? candidateLimit : null"
|
||||
@update:value="candidateLimit = $event ?? 0"
|
||||
:min="1"
|
||||
:step="1"
|
||||
></NInputNumber>
|
||||
</label>
|
||||
<NCheckbox
|
||||
v-model:checked="force"
|
||||
:disabled="operation.pending"
|
||||
class="flex gap-2 pb-2 text-xs"
|
||||
>覆盖未锁定身份文本
|
||||
</NCheckbox>
|
||||
<ConfirmAction
|
||||
:label="force ? '重生成角色身份文本' : '补齐角色身份文本'"
|
||||
:disabled="blocked || !hasStyle || !batchValid || dirty || !casting.total"
|
||||
acknowledgement
|
||||
description="只生成 Character Identity 文本,不生成图片。已锁定角色始终跳过;覆盖文本不会自动更新已有候选图。"
|
||||
@confirm="generateProject(true)"
|
||||
/>
|
||||
<ConfirmAction
|
||||
label="生成选角候选"
|
||||
:disabled="
|
||||
castingBlocked ||
|
||||
!hasStyle ||
|
||||
!batchValid ||
|
||||
!candidateLimitValid ||
|
||||
!casting.missingAnchor
|
||||
"
|
||||
acknowledgement
|
||||
description="最多按本批上限为缺少 Anchor 的 Character 调用 Seedream,各自独立生成一张停用的 primary 候选,不复用旧身份母版。已有候选、已有母版或已完成选角的角色不会重复处理。"
|
||||
@confirm="generateCastingCandidates"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="!batchValid || !candidateLimitValid" class="mt-3 text-xs text-danger">
|
||||
并发数与本批上限都必须是正整数。
|
||||
</p>
|
||||
<div class="casting-list mt-5">
|
||||
<NButton
|
||||
v-for="item in casting.items"
|
||||
:key="item.subjectId"
|
||||
@click="select(item.subjectId)"
|
||||
class="casting-item"
|
||||
:class="{ selected: selectedId === item.subjectId }"
|
||||
><span class="min-w-0">
|
||||
<span class="block truncate text-xs font-medium">{{ item.subjectName }}</span>
|
||||
<span class="mt-1 block font-mono text-[10px] text-muted">{{
|
||||
item.subjectRef
|
||||
}}</span> </span
|
||||
><StatusBadge :status="item.status" :label="castingStatusLabel(item.status)"
|
||||
/></NButton>
|
||||
</div>
|
||||
</section>
|
||||
<NCollapse class="panel mb-5 p-5"
|
||||
><NCollapseItem name="details"
|
||||
><template #header>高级:全部主体身份文本</template>
|
||||
<p class="mt-3 text-xs leading-6 text-muted">
|
||||
同时处理 Character、Scene 和
|
||||
Prop;角色选角建议优先使用上方专用入口。已锁定身份始终跳过。
|
||||
</p>
|
||||
<ConfirmAction
|
||||
class="mt-4"
|
||||
:label="force ? '重生成全部未锁定身份' : '补齐全部主体身份文本'"
|
||||
:disabled="blocked || !hasStyle || !batchValid || dirty || !subjects.length"
|
||||
acknowledgement
|
||||
description="生成全项目主体身份描述与提示词,不生成图片。覆盖不会自动更新旧身份图、形态图、首帧或视频。"
|
||||
@confirm="generateProject(false)" /></NCollapseItem
|
||||
></NCollapse>
|
||||
<section v-if="session.receipt" class="panel mb-5 p-5" aria-label="身份文本批量回执">
|
||||
<div class="flex justify-between gap-3">
|
||||
<h3 class="text-sm font-medium">{{ session.receipt.title }} · 本次回执</h3>
|
||||
<NButton @click="exportReceipt" text size="small">导出诊断</NButton>
|
||||
</div>
|
||||
<p v-if="session.receipt.kind === 'identities'" 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.skippedLocked }})· 失败 {{ session.receipt.result.failed }}
|
||||
</p>
|
||||
<p v-else class="mt-3 text-xs text-muted">
|
||||
共 {{ session.receipt.result.totalCharacters }} 个角色 · 缺候选
|
||||
{{ session.receipt.result.missingAnchor }} · 本批目标
|
||||
{{ session.receipt.result.targetCount }} · 已生成 {{ session.receipt.result.generated }} ·
|
||||
失败 {{ session.receipt.result.failed }}
|
||||
</p>
|
||||
<NAlert
|
||||
v-if="session.receipt.result.failed"
|
||||
role="alert"
|
||||
type="error"
|
||||
:show-icon="false"
|
||||
class="mt-3"
|
||||
>
|
||||
本次存在部分失败,成功结果不会回滚。请检查逐角色原因后再重试。
|
||||
><NAlert role="alert" type="error" :show-icon="false"
|
||||
>{{ catalog.error.value || styleQuery.error.value || castingQuery.error.value }}
|
||||
当前相关操作已暂停,请刷新核对后端。
|
||||
</NAlert></NScrollbar
|
||||
>
|
||||
<NScrollbar class="workspace-overview" content-class="overview-scroll-content"
|
||||
><NCollapse v-model:expanded-names="overview"
|
||||
><NCollapseItem name="casting" title="角色选角总览与批量操作"
|
||||
><div class="overview-content">
|
||||
<NAlert type="info" :show-icon="false" class="mt-5 text-xs">
|
||||
Character 现在需要依次完成身份文本、选角候选、确认演员并锁定
|
||||
Identity,之后首帧才能就绪。未明确人物背景时,AI
|
||||
默认以中国人物作为选角基线;项目和角色的明确事实始终优先。场景和道具仍使用普通身份母版流程。
|
||||
</NAlert>
|
||||
<ul class="mt-3 space-y-2 text-xs text-danger">
|
||||
<li v-for="failure in session.receipt.result.failures" :key="failure.subjectId">
|
||||
{{ failure.subjectRef }} · {{ failure.subjectId }}:{{ failure.error }}
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mt-3 text-[11px] text-muted">
|
||||
回执仅保留于当前浏览器会话;最终状态以刷新后的正式记录为准。
|
||||
</p>
|
||||
</section>
|
||||
</div></NCollapseItem
|
||||
></NCollapse
|
||||
<NAlert v-if="styleQuery.data.value && !hasStyle" type="info" :show-icon="false" class="mt-4">
|
||||
先创建项目视觉风格,才能 AI 生成身份或身份图片。<RouterLink
|
||||
:to="`/projects/${projectId}/visual-style`"
|
||||
class="text-button ml-2"
|
||||
>设置视觉风格 →</RouterLink
|
||||
></NAlert
|
||||
>
|
||||
<section v-if="casting" class="panel my-5 p-5" aria-label="角色选角流程">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<span class="production-step"><UserCheck :size="17" /></span>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold">角色选角</h3>
|
||||
<p class="mt-2 text-xs leading-6 text-muted">
|
||||
只处理 Character。每张候选都从 Identity 独立生成,不复用旧
|
||||
Anchor;确认选择时才会同时切换 Anchor 并锁定 Identity。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="font-mono text-xs text-muted"
|
||||
>{{ casting.ready }} / {{ casting.total }} 已完成</span
|
||||
>
|
||||
</div>
|
||||
<dl class="casting-stats mt-4">
|
||||
<div>
|
||||
<dt>缺身份文本</dt>
|
||||
<dd>{{ casting.missingIdentity }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>缺候选图</dt>
|
||||
<dd>{{ casting.missingAnchor }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>待确认演员</dt>
|
||||
<dd>{{ casting.candidatePending }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>母版未锁定</dt>
|
||||
<dd>{{ casting.unlocked }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>选角完成</dt>
|
||||
<dd>{{ casting.ready }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="mt-5 flex flex-wrap items-end gap-4 border-t border-line pt-4">
|
||||
<label class="w-28">
|
||||
<span class="field-label">并发数</span>
|
||||
<NInputNumber
|
||||
:disabled="operation.pending"
|
||||
:value="typeof concurrency === 'number' ? concurrency : null"
|
||||
@update:value="concurrency = $event ?? 0"
|
||||
:min="1"
|
||||
:step="1"
|
||||
></NInputNumber>
|
||||
</label>
|
||||
<label class="w-28">
|
||||
<span class="field-label">本批上限</span>
|
||||
<NInputNumber
|
||||
:disabled="operation.pending"
|
||||
:value="typeof candidateLimit === 'number' ? candidateLimit : null"
|
||||
@update:value="candidateLimit = $event ?? 0"
|
||||
:min="1"
|
||||
:step="1"
|
||||
></NInputNumber>
|
||||
</label>
|
||||
<NCheckbox
|
||||
v-model:checked="force"
|
||||
:disabled="operation.pending"
|
||||
class="flex gap-2 pb-2 text-xs"
|
||||
>覆盖未锁定身份文本
|
||||
</NCheckbox>
|
||||
<ConfirmAction
|
||||
:label="force ? '重生成角色身份文本' : '补齐角色身份文本'"
|
||||
:disabled="blocked || !hasStyle || !batchValid || dirty || !casting.total"
|
||||
acknowledgement
|
||||
description="只生成 Character Identity 文本,不生成图片。已锁定角色始终跳过;覆盖文本不会自动更新已有候选图。"
|
||||
@confirm="generateProject(true)"
|
||||
/>
|
||||
<ConfirmAction
|
||||
label="生成选角候选"
|
||||
:disabled="
|
||||
castingBlocked ||
|
||||
!hasStyle ||
|
||||
!batchValid ||
|
||||
!candidateLimitValid ||
|
||||
!casting.missingAnchor
|
||||
"
|
||||
acknowledgement
|
||||
description="最多按本批上限为缺少 Anchor 的 Character 调用 Seedream,各自独立生成一张停用的 primary 候选,不复用旧身份母版。已有候选、已有母版或已完成选角的角色不会重复处理。"
|
||||
@confirm="generateCastingCandidates"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="!batchValid || !candidateLimitValid" class="mt-3 text-xs text-danger">
|
||||
并发数与本批上限都必须是正整数。
|
||||
</p>
|
||||
<div class="casting-list mt-5">
|
||||
<NButton
|
||||
v-for="item in casting.items"
|
||||
:key="item.subjectId"
|
||||
@click="select(item.subjectId)"
|
||||
class="casting-item"
|
||||
:class="{ selected: selectedId === item.subjectId }"
|
||||
><span class="min-w-0">
|
||||
<span class="block truncate text-xs font-medium">{{ item.subjectName }}</span>
|
||||
<span class="mt-1 block font-mono text-[10px] text-muted">{{
|
||||
item.subjectRef
|
||||
}}</span> </span
|
||||
><StatusBadge :status="item.status" :label="castingStatusLabel(item.status)"
|
||||
/></NButton>
|
||||
</div>
|
||||
</section>
|
||||
<NCollapse class="panel mb-5 p-5"
|
||||
><NCollapseItem name="details"
|
||||
><template #header>高级:全部主体身份文本</template>
|
||||
<p class="mt-3 text-xs leading-6 text-muted">
|
||||
同时处理 Character、Scene 和
|
||||
Prop;角色选角建议优先使用上方专用入口。已锁定身份始终跳过。
|
||||
</p>
|
||||
<ConfirmAction
|
||||
class="mt-4"
|
||||
:label="force ? '重生成全部未锁定身份' : '补齐全部主体身份文本'"
|
||||
:disabled="blocked || !hasStyle || !batchValid || dirty || !subjects.length"
|
||||
acknowledgement
|
||||
description="生成全项目主体身份描述与提示词,不生成图片。覆盖不会自动更新旧身份图、形态图、首帧或视频。"
|
||||
@confirm="generateProject(false)" /></NCollapseItem
|
||||
></NCollapse>
|
||||
<section v-if="session.receipt" class="panel mb-5 p-5" aria-label="身份文本批量回执">
|
||||
<div class="flex justify-between gap-3">
|
||||
<h3 class="text-sm font-medium">{{ session.receipt.title }} · 本次回执</h3>
|
||||
<NButton @click="exportReceipt" text size="small">导出诊断</NButton>
|
||||
</div>
|
||||
<p v-if="session.receipt.kind === 'identities'" 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.skippedLocked }})· 失败 {{ session.receipt.result.failed }}
|
||||
</p>
|
||||
<p v-else class="mt-3 text-xs text-muted">
|
||||
共 {{ session.receipt.result.totalCharacters }} 个角色 · 缺候选
|
||||
{{ session.receipt.result.missingAnchor }} · 本批目标
|
||||
{{ session.receipt.result.targetCount }} · 已生成
|
||||
{{ session.receipt.result.generated }} · 失败 {{ session.receipt.result.failed }}
|
||||
</p>
|
||||
<NAlert
|
||||
v-if="session.receipt.result.failed"
|
||||
role="alert"
|
||||
type="error"
|
||||
:show-icon="false"
|
||||
class="mt-3"
|
||||
>
|
||||
本次存在部分失败,成功结果不会回滚。请检查逐角色原因后再重试。
|
||||
</NAlert>
|
||||
<ul class="mt-3 space-y-2 text-xs text-danger">
|
||||
<li v-for="failure in session.receipt.result.failures" :key="failure.subjectId">
|
||||
{{ failure.subjectRef }} · {{ failure.subjectId }}:{{ failure.error }}
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mt-3 text-[11px] text-muted">
|
||||
回执仅保留于当前浏览器会话;最终状态以刷新后的正式记录为准。
|
||||
</p>
|
||||
</section>
|
||||
</div></NCollapseItem
|
||||
></NCollapse
|
||||
></NScrollbar
|
||||
>
|
||||
<div v-if="subjects.length" class="identity-workspace">
|
||||
<aside class="panel min-w-0 p-4">
|
||||
@@ -346,7 +358,10 @@ watch(
|
||||
]"
|
||||
class="mt-3"
|
||||
></NSelect>
|
||||
<div class="identity-subject-list mt-4">
|
||||
<NScrollbar
|
||||
class="identity-subject-list panel-scroll mt-4"
|
||||
content-class="identity-subject-list-content"
|
||||
>
|
||||
<NButton
|
||||
v-for="item in filtered"
|
||||
:key="item.id"
|
||||
@@ -367,7 +382,7 @@ watch(
|
||||
></NButton
|
||||
>
|
||||
<p v-if="!filtered.length" class="py-6 text-xs text-muted">没有匹配主体,请调整筛选。</p>
|
||||
</div>
|
||||
</NScrollbar>
|
||||
<NButton
|
||||
:disabled="catalog.loading.value || castingQuery.loading.value"
|
||||
@click="refreshDirectory"
|
||||
@@ -378,7 +393,7 @@ watch(
|
||||
刷新主体目录
|
||||
</NButton>
|
||||
</aside>
|
||||
<div class="min-w-0">
|
||||
<NScrollbar class="identity-detail panel-scroll min-w-0">
|
||||
<NAlert v-if="pendingSelection" type="info" :show-icon="false" class="mb-4"
|
||||
><p class="text-xs">当前主体有未保存修改,切换会丢弃草稿。</p>
|
||||
<div class="mt-3 flex gap-3">
|
||||
@@ -495,7 +510,7 @@ watch(
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</NScrollbar>
|
||||
</div>
|
||||
<p v-else-if="catalog.loading.value" class="py-8 text-sm text-muted" role="status">正在读取正式主体目录…</p>
|
||||
<EmptyState
|
||||
|
||||
Reference in New Issue
Block a user