refactor: 精简导航并扩大工作区内容显示面积
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import WorkspacePage from '../../components/ui/WorkspacePage.vue'
|
||||
import WorkspaceTools from '../../components/ui/WorkspaceTools.vue'
|
||||
import {
|
||||
NScrollbar,
|
||||
NAlert,
|
||||
@@ -81,174 +82,235 @@ function exportEpisode() {
|
||||
)
|
||||
}
|
||||
/** 回执到达后打开诊断区;平时留出镜头检查空间。 */
|
||||
const overview = ref<string[]>([])
|
||||
const toolsOpen = ref(false)
|
||||
const overview = ref<string[]>(['generation'])
|
||||
watch(
|
||||
() => session.value.receipt,
|
||||
receipt => {
|
||||
if (receipt) overview.value = ['generation']
|
||||
},
|
||||
{ immediate: true }
|
||||
if (receipt) {
|
||||
overview.value = ['generation']
|
||||
toolsOpen.value = true
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WorkspacePage split class="storyboard-workspace-page"
|
||||
<WorkspacePage split compact class="storyboard-workspace-page"
|
||||
><template #header
|
||||
><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}/breakdown`" class="text-button"
|
||||
><ArrowLeft :size="14" />回到拆解</RouterLink
|
||||
>
|
||||
</div>
|
||||
<section class="mt-3" aria-label="分镜生成配置">
|
||||
<div class="storyboard-controls">
|
||||
<label
|
||||
><span class="field-label">当前剧集</span
|
||||
><NSelect
|
||||
:value="episodeNo"
|
||||
aria-label="选择分镜剧集"
|
||||
@update:value="selectedEpisode = Number($event)"
|
||||
:options="[
|
||||
...(!episodeOptions.length ? [{ label: String('暂无剧集'), value: 0 }] : []),
|
||||
...episodeOptions.map(episode => ({
|
||||
label: String(' 第 ' + episode.episodeNo + ' 集 · ' + episode.title),
|
||||
value: episode.episodeNo
|
||||
}))
|
||||
]"
|
||||
class="select-control"
|
||||
></NSelect
|
||||
></label>
|
||||
<label
|
||||
><span class="field-label">批量并发</span
|
||||
><NInputNumber
|
||||
:disabled="operation.pending"
|
||||
:input-props="{ id: 'storyboard-concurrency' }"
|
||||
:value="typeof concurrency === 'number' ? concurrency : null"
|
||||
@update:value="concurrency = $event ?? 0"
|
||||
:min="1"
|
||||
:step="1"
|
||||
></NInputNumber
|
||||
><span v-if="!batchValid" class="mt-1 block text-xs text-danger">请输入正整数</span></label
|
||||
>
|
||||
<label
|
||||
><span class="field-label">状态自动修复次数</span
|
||||
><NInputNumber
|
||||
:disabled="operation.pending"
|
||||
:input-props="{ id: 'storyboard-repairs' }"
|
||||
:value="typeof maxRepairAttempts === 'number' ? maxRepairAttempts : null"
|
||||
@update:value="maxRepairAttempts = $event ?? 0"
|
||||
:min="0"
|
||||
:step="1"
|
||||
></NInputNumber
|
||||
><span v-if="!repairsValid" class="mt-1 block text-xs text-danger"
|
||||
>请输入非负整数,0 表示不修复</span
|
||||
></label
|
||||
>
|
||||
<NCheckbox
|
||||
v-model:checked="force"
|
||||
:disabled="operation.pending"
|
||||
class="flex items-center gap-2 pb-3 text-xs"
|
||||
>批量覆盖已有结果</NCheckbox
|
||||
><div class="workspace-toolbar">
|
||||
<label class="toolbar-episode"
|
||||
><span>剧集</span
|
||||
><NSelect
|
||||
:value="episodeNo"
|
||||
aria-label="选择分镜剧集"
|
||||
@update:value="selectedEpisode = Number($event)"
|
||||
:options="[
|
||||
...(!episodeOptions.length ? [{ label: String('暂无剧集'), value: 0 }] : []),
|
||||
...episodeOptions.map(episode => ({
|
||||
label: String(' 第 ' + episode.episodeNo + ' 集 · ' + episode.title),
|
||||
value: episode.episodeNo
|
||||
}))
|
||||
]"
|
||||
class="select-control"
|
||||
></NSelect
|
||||
></label>
|
||||
<div class="toolbar-metrics">
|
||||
<span v-for="item in coverage" :key="item.title"
|
||||
>{{ item.title }} {{ item.count }} / {{ item.total }}</span
|
||||
>
|
||||
</div>
|
||||
</section></template
|
||||
<div class="toolbar-actions">
|
||||
<div class="flex gap-3">
|
||||
<NButton :disabled="query.loading.value" @click="query.refresh" text size="small"
|
||||
><RefreshCw :size="13" :class="{ 'animate-spin': query.loading.value }" />刷新分镜</NButton
|
||||
><NButton :disabled="!shots.length" @click="exportEpisode" text size="small"
|
||||
><Download :size="13" />导出本集
|
||||
</NButton>
|
||||
</div>
|
||||
|
||||
<WorkspaceTools
|
||||
v-model:open="toolsOpen"
|
||||
title="分镜生成配置与回执"
|
||||
:has-receipt="!!session.receipt"
|
||||
>
|
||||
<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}/breakdown`" class="text-button"
|
||||
><ArrowLeft :size="14" />回到拆解</RouterLink
|
||||
>
|
||||
</div>
|
||||
<section class="mt-3" aria-label="分镜生成配置">
|
||||
<div class="storyboard-controls">
|
||||
<label
|
||||
><span class="field-label">批量并发</span
|
||||
><NInputNumber
|
||||
:disabled="operation.pending"
|
||||
:input-props="{ id: 'storyboard-concurrency' }"
|
||||
:value="typeof concurrency === 'number' ? concurrency : null"
|
||||
@update:value="concurrency = $event ?? 0"
|
||||
:min="1"
|
||||
:step="1"
|
||||
></NInputNumber
|
||||
><span v-if="!batchValid" class="mt-1 block text-xs text-danger"
|
||||
>请输入正整数</span
|
||||
></label
|
||||
>
|
||||
<label
|
||||
><span class="field-label">状态自动修复次数</span
|
||||
><NInputNumber
|
||||
:disabled="operation.pending"
|
||||
:input-props="{ id: 'storyboard-repairs' }"
|
||||
:value="typeof maxRepairAttempts === 'number' ? maxRepairAttempts : null"
|
||||
@update:value="maxRepairAttempts = $event ?? 0"
|
||||
:min="0"
|
||||
:step="1"
|
||||
></NInputNumber
|
||||
><span v-if="!repairsValid" class="mt-1 block text-xs text-danger"
|
||||
>请输入非负整数,0 表示不修复</span
|
||||
></label
|
||||
>
|
||||
<NCheckbox
|
||||
v-model:checked="force"
|
||||
:disabled="operation.pending"
|
||||
class="flex items-center gap-2 pb-3 text-xs"
|
||||
>批量覆盖已有结果</NCheckbox
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
<div class="storyboard-coverage grid grid-cols-2 gap-3">
|
||||
<div v-for="item in coverage" :key="item.title" class="panel px-3 py-2">
|
||||
<div class="mb-3 flex items-center justify-between text-xs">
|
||||
<span>{{ item.title }}</span
|
||||
><span class="font-mono text-muted">{{ item.count }} / {{ item.total }} 镜头</span>
|
||||
</div>
|
||||
<NProgress
|
||||
:aria-label="item.title + '已保存覆盖率'"
|
||||
type="line"
|
||||
:show-indicator="false"
|
||||
:height="4"
|
||||
:percentage="
|
||||
Math.min(
|
||||
100,
|
||||
Math.max(0, (item.count / Math.max(1, Math.max(item.total, 1))) * 100)
|
||||
)
|
||||
"
|
||||
class=""
|
||||
></NProgress>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workspace-overview-panel">
|
||||
<NCollapse v-model:expanded-names="overview"
|
||||
><NCollapseItem name="generation" title="生成配置、批量操作与回执"
|
||||
><div class="overview-content">
|
||||
<div class="generation-row">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold">
|
||||
<span class="mr-2 font-mono text-xs text-muted">01</span
|
||||
>导演摄影设计
|
||||
</h3>
|
||||
<p class="mt-2 text-xs leading-6 text-muted">
|
||||
景别、机位、运镜与构图。单集操作会重新生成并覆盖;批量默认跳过完整剧集。
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<ConfirmAction
|
||||
label="生成本集导演设计"
|
||||
:disabled="!allowed['direction-one']"
|
||||
acknowledgement
|
||||
description="调用模型生成当前整集的导演设计,校验通过后保存。如果此集已有设计,将覆盖原结果。"
|
||||
@confirm="run('direction-one')"
|
||||
/><ConfirmAction
|
||||
:label="force ? '重生成全部导演设计' : '补齐项目导演设计'"
|
||||
:disabled="!allowed['direction-all']"
|
||||
acknowledgement
|
||||
:description="
|
||||
force
|
||||
? '重新生成项目全部剧集的导演设计,覆盖已有结果。'
|
||||
: '对整个项目生成导演设计,跳过已完整保存的剧集,可用于重试失败或未完成的剧集。'
|
||||
"
|
||||
@confirm="run('direction-all')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="generation-row">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold">
|
||||
<span class="mr-2 font-mono text-xs text-muted">02</span
|
||||
>即时视觉状态
|
||||
</h3>
|
||||
<p class="mt-2 text-xs leading-6 text-muted">
|
||||
表情、姿态、位置、环境与连续性。每集需要完整导演设计;批量中缺少前置设计的剧集会单独报错。
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<ConfirmAction
|
||||
label="生成本集即时状态"
|
||||
:disabled="!allowed['visual-one']"
|
||||
acknowledgement
|
||||
description="依据本集导演设计和主体形态生成即时视觉状态,按配置尝试自动修复。校验通过才保存,已有状态将被覆盖。"
|
||||
@confirm="run('visual-one')"
|
||||
/><ConfirmAction
|
||||
:label="force ? '重生成全部即时状态' : '补齐项目即时状态'"
|
||||
:disabled="!allowed['visual-all']"
|
||||
acknowledgement
|
||||
:description="
|
||||
force
|
||||
? '重新生成整个项目的即时状态,覆盖已有结果。每集必须先有完整导演设计。'
|
||||
: '补齐项目即时状态,跳过完整剧集;未完成导演设计的剧集会返回失败原因。'
|
||||
"
|
||||
@confirm="run('visual-all')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-4 text-[11px] leading-6 text-muted">
|
||||
修改或覆盖上游设计后,后端不会自动使旧状态、旧提示词失效;请按顺序重生成下游内容。自动修复次数仅用于
|
||||
VisualState。
|
||||
</p>
|
||||
<NCollapse class="mt-3 border-t border-line pt-3"
|
||||
><NCollapseItem name="details"
|
||||
><template #header>视频提示词批量操作</template>
|
||||
<p class="my-3 text-xs leading-6 text-muted">
|
||||
后端会先校验 GenerationSpec
|
||||
与主体参考图。这里只生成提示词;首帧、视频任务、就绪诊断与失败重试已整理到
|
||||
<RouterLink
|
||||
:to="`/projects/${id}/production`"
|
||||
class="text-button mx-1"
|
||||
>镜头生产</RouterLink
|
||||
>。
|
||||
</p>
|
||||
<ConfirmAction
|
||||
:label="force ? '重生成全部视频提示词' : '补齐项目视频提示词'"
|
||||
:disabled="!allowed.prompts"
|
||||
acknowledgement
|
||||
:description="
|
||||
force
|
||||
? '调用模型重新生成全项目镜头的视频提示词,覆盖已有正文。'
|
||||
: '调用模型生成缺少视频提示词的镜头,已有提示词直接跳过。参考图缺失会返回失败。'
|
||||
"
|
||||
@confirm="run('prompts')" /></NCollapseItem
|
||||
></NCollapse>
|
||||
<GenerationReceipt
|
||||
v-if="session.receipt"
|
||||
:receipt="session.receipt"
|
||||
/></div></NCollapseItem
|
||||
></NCollapse>
|
||||
</div>
|
||||
</WorkspaceTools>
|
||||
</div></div
|
||||
></template>
|
||||
<NScrollbar
|
||||
v-if="
|
||||
query.error.value ||
|
||||
!prerequisites.directionEpisode ||
|
||||
!prerequisites.visualEpisode ||
|
||||
(data && !directionComplete && data.directions.shotCount)
|
||||
"
|
||||
class="workspace-feedback"
|
||||
>
|
||||
<NScrollbar class="workspace-overview" content-class="overview-scroll-content"
|
||||
><NCollapse v-model:expanded-names="overview"
|
||||
><NCollapseItem name="generation" title="生成配置、批量操作与回执"
|
||||
><div class="overview-content">
|
||||
<div class="generation-row">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold">
|
||||
<span class="mr-2 font-mono text-xs text-muted">01</span>导演摄影设计
|
||||
</h3>
|
||||
<p class="mt-2 text-xs leading-6 text-muted">
|
||||
景别、机位、运镜与构图。单集操作会重新生成并覆盖;批量默认跳过完整剧集。
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<ConfirmAction
|
||||
label="生成本集导演设计"
|
||||
:disabled="!allowed['direction-one']"
|
||||
acknowledgement
|
||||
description="调用模型生成当前整集的导演设计,校验通过后保存。如果此集已有设计,将覆盖原结果。"
|
||||
@confirm="run('direction-one')"
|
||||
/><ConfirmAction
|
||||
:label="force ? '重生成全部导演设计' : '补齐项目导演设计'"
|
||||
:disabled="!allowed['direction-all']"
|
||||
acknowledgement
|
||||
:description="
|
||||
force
|
||||
? '重新生成项目全部剧集的导演设计,覆盖已有结果。'
|
||||
: '对整个项目生成导演设计,跳过已完整保存的剧集,可用于重试失败或未完成的剧集。'
|
||||
"
|
||||
@confirm="run('direction-all')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="generation-row">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold">
|
||||
<span class="mr-2 font-mono text-xs text-muted">02</span>即时视觉状态
|
||||
</h3>
|
||||
<p class="mt-2 text-xs leading-6 text-muted">
|
||||
表情、姿态、位置、环境与连续性。每集需要完整导演设计;批量中缺少前置设计的剧集会单独报错。
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<ConfirmAction
|
||||
label="生成本集即时状态"
|
||||
:disabled="!allowed['visual-one']"
|
||||
acknowledgement
|
||||
description="依据本集导演设计和主体形态生成即时视觉状态,按配置尝试自动修复。校验通过才保存,已有状态将被覆盖。"
|
||||
@confirm="run('visual-one')"
|
||||
/><ConfirmAction
|
||||
:label="force ? '重生成全部即时状态' : '补齐项目即时状态'"
|
||||
:disabled="!allowed['visual-all']"
|
||||
acknowledgement
|
||||
:description="
|
||||
force
|
||||
? '重新生成整个项目的即时状态,覆盖已有结果。每集必须先有完整导演设计。'
|
||||
: '补齐项目即时状态,跳过完整剧集;未完成导演设计的剧集会返回失败原因。'
|
||||
"
|
||||
@confirm="run('visual-all')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-4 text-[11px] leading-6 text-muted">
|
||||
修改或覆盖上游设计后,后端不会自动使旧状态、旧提示词失效;请按顺序重生成下游内容。自动修复次数仅用于
|
||||
VisualState。
|
||||
</p>
|
||||
<NCollapse class="mt-3 border-t border-line pt-3"
|
||||
><NCollapseItem name="details"
|
||||
><template #header>视频提示词批量操作</template>
|
||||
<p class="my-3 text-xs leading-6 text-muted">
|
||||
后端会先校验 GenerationSpec
|
||||
与主体参考图。这里只生成提示词;首帧、视频任务、就绪诊断与失败重试已整理到
|
||||
<RouterLink :to="`/projects/${id}/production`" class="text-button mx-1"
|
||||
>镜头生产</RouterLink
|
||||
>。
|
||||
</p>
|
||||
<ConfirmAction
|
||||
:label="force ? '重生成全部视频提示词' : '补齐项目视频提示词'"
|
||||
:disabled="!allowed.prompts"
|
||||
acknowledgement
|
||||
:description="
|
||||
force
|
||||
? '调用模型重新生成全项目镜头的视频提示词,覆盖已有正文。'
|
||||
: '调用模型生成缺少视频提示词的镜头,已有提示词直接跳过。参考图缺失会返回失败。'
|
||||
"
|
||||
@confirm="run('prompts')" /></NCollapseItem
|
||||
></NCollapse>
|
||||
<GenerationReceipt
|
||||
v-if="session.receipt"
|
||||
:receipt="session.receipt"
|
||||
/></div></NCollapseItem></NCollapse
|
||||
></NScrollbar>
|
||||
<NScrollbar class="workspace-feedback">
|
||||
<NAlert v-if="query.error.value" role="alert" type="error" :show-icon="false" class="mt-4"
|
||||
>{{ query.error.value }} 当前保留上次成功读取的数据,暂停生成操作。<NButton
|
||||
@click="query.refresh"
|
||||
@@ -275,37 +337,6 @@ watch(
|
||||
本集导演设计尚未补齐,请先完成第 01 步,再生成本集即时状态。
|
||||
</NAlert>
|
||||
</NScrollbar>
|
||||
<div class="my-6 flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold">第 {{ episodeNo || '—' }} 集 · 镜头检查</h3>
|
||||
<p class="mt-2 text-[11px] text-muted">
|
||||
当前剧集数据库结果每 6 秒刷新。覆盖率不是批量任务进度;批量诊断在请求返回后显示。
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<NButton :disabled="query.loading.value" @click="query.refresh" text size="small"
|
||||
><RefreshCw :size="13" :class="{ 'animate-spin': query.loading.value }" />刷新分镜</NButton
|
||||
><NButton :disabled="!shots.length" @click="exportEpisode" text size="small"
|
||||
><Download :size="13" />导出本集
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="storyboard-coverage grid grid-cols-2 gap-3">
|
||||
<div v-for="item in coverage" :key="item.title" class="panel px-3 py-2">
|
||||
<div class="mb-3 flex items-center justify-between text-xs">
|
||||
<span>{{ item.title }}</span
|
||||
><span class="font-mono text-muted">{{ item.count }} / {{ item.total }} 镜头</span>
|
||||
</div>
|
||||
<NProgress
|
||||
:aria-label="item.title + '已保存覆盖率'"
|
||||
type="line"
|
||||
:show-indicator="false"
|
||||
:height="4"
|
||||
:percentage="Math.min(100, Math.max(0, (item.count / Math.max(1, Math.max(item.total, 1))) * 100))"
|
||||
class=""
|
||||
></NProgress>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!data && query.loading.value" class="panel p-8 text-sm text-muted" role="status">
|
||||
正在读取正式分镜数据……
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user