refactor: 使用 Naive UI 重构后台布局与黑白双主题
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import WorkspacePage from '../../components/ui/WorkspacePage.vue'
|
||||
import { NCollapse, NCollapseItem, NAlert, NButton, NCheckbox, NInputNumber, NProgress, NSelect } from 'naive-ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { ArrowLeft, CheckCircle2, CircleAlert, Film, Image, MessageSquareText, RefreshCw } from '@lucide/vue'
|
||||
import { ProgressRoot, ProgressIndicator } from 'reka-ui'
|
||||
import { EmptyState, StatusBadge } from '../../components/ui'
|
||||
import ConfirmAction from '../workflows/ConfirmAction.vue'
|
||||
import { issueLabel, productionStatusLabel } from './model'
|
||||
@@ -31,7 +32,6 @@ const {
|
||||
blocked,
|
||||
run
|
||||
} = useProduction()
|
||||
const staleKeyframes = computed(() => query.data.value?.keyframes.stalePrimaryKeyframe ?? 0)
|
||||
const staleVideos = computed(() => query.data.value?.videos.stalePrimaryVideo ?? 0)
|
||||
const stages = computed(() => {
|
||||
const data = query.data.value
|
||||
@@ -87,186 +87,238 @@ const identityBlocked = computed(
|
||||
(query.data.value?.keyframes.missingIdentityAnchor ?? 0) +
|
||||
(query.data.value?.keyframes.identityUnlocked ?? 0)
|
||||
)
|
||||
const staleKeyframes = computed(() => query.data.value?.keyframes.stalePrimaryKeyframe ?? 0)
|
||||
|
||||
/** 父页查询包含整个项目的就绪统计,单镜头变更后立即刷新。 */
|
||||
function refreshProject() {
|
||||
void query.refresh()
|
||||
}
|
||||
/** 新回执自动展开,避免折叠区隐藏部分失败诊断。 */
|
||||
const overview = ref<string[]>([])
|
||||
watch(
|
||||
() => session.value.receipt,
|
||||
receipt => {
|
||||
if (receipt) overview.value = ['overview']
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="mt-7">
|
||||
<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">
|
||||
<ArrowLeft :size="14" />回到分镜设计
|
||||
</RouterLink>
|
||||
</div>
|
||||
<p class="alert mt-5 text-xs">
|
||||
每一步都按后端就绪检查执行。覆盖只会新增首帧/视频候选,不会替换当前主资产;上游参考资产变化后,下游过期结果会被标记并阻止继续使用,需要重新生成后才能恢复就绪。
|
||||
</p>
|
||||
|
||||
<section class="panel mt-5 p-5" aria-label="项目生产配置">
|
||||
<div class="production-controls">
|
||||
<label>
|
||||
<span class="field-label">当前剧集</span>
|
||||
<select
|
||||
:value="episodeNo"
|
||||
class="input"
|
||||
aria-label="选择生产剧集"
|
||||
@change="selectedEpisode = Number(($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option v-if="!episodeOptions.length" :value="0">暂无剧集</option>
|
||||
<option v-for="episode in episodeOptions" :key="episode.episodeNo" :value="episode.episodeNo">
|
||||
第 {{ episode.episodeNo }} 集 · {{ episode.title }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span class="field-label">批量并发</span>
|
||||
<input
|
||||
v-model.number="concurrency"
|
||||
class="input"
|
||||
type="number"
|
||||
min="1"
|
||||
step="1"
|
||||
:disabled="operation.pending"
|
||||
/>
|
||||
<span v-if="!batchValid" class="mt-1 block text-xs text-danger">请输入正整数</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 pb-3 text-xs">
|
||||
<input
|
||||
v-model="force"
|
||||
type="checkbox"
|
||||
class="accent-accent"
|
||||
:disabled="operation.pending"
|
||||
/>覆盖模式:为已有结果新增候选
|
||||
</label>
|
||||
<button class="text-button mb-2 ml-auto" :disabled="query.loading.value" @click="query.refresh">
|
||||
<RefreshCw :size="13" :class="{ 'animate-spin': query.loading.value }" />刷新就绪状态
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-3 text-[11px] leading-6 text-muted">
|
||||
批量操作始终面向整个项目,不受当前剧集选择影响;当前剧集只控制下方逐镜检查。Seedream 与 Seedance
|
||||
均可能产生费用。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<p v-if="query.error.value" class="alert alert-error mt-4" role="alert">
|
||||
{{ query.error.value }}<span v-if="query.data.value"> 当前保留上次读取的数据,生产操作已暂停。</span>
|
||||
</p>
|
||||
<p v-if="videoRunning" class="alert mt-4" role="status">
|
||||
当前有 {{ videoRunning }} 个视频任务等待或生成中。后端正在轮询 Provider;同一镜头不会重复提交活动任务。
|
||||
</p>
|
||||
<p v-if="identityBlocked" class="alert mt-4">
|
||||
当前有 {{ identityBlocked }} 项角色身份前置问题。Character 必须生成
|
||||
Identity、确认演员母版并锁定后,首帧才能就绪。
|
||||
<RouterLink :to="`/projects/${id}/subject-identity`" class="text-button ml-2">前往角色选角 →</RouterLink>
|
||||
</p>
|
||||
<p v-if="staleKeyframes" class="alert mt-4" role="status">
|
||||
当前有 {{ staleKeyframes }} 个主首帧已过期。它们使用的主体参考资产已经变化,视频阶段会被阻止;可在下方逐镜重新生成,或使用“补齐 / 更新主首帧”批量处理。
|
||||
</p>
|
||||
<p v-if="staleVideos" class="alert mt-4" role="status">
|
||||
当前有 {{ staleVideos }} 个主视频已过期。它们使用的主首帧或主体参考图已经变化;非覆盖模式批量生成时会只重建这些过期主视频。
|
||||
</p>
|
||||
<ProductionReceipt v-if="session.receipt" :receipt="session.receipt" />
|
||||
|
||||
<div v-if="query.data.value" class="production-pipeline mt-5">
|
||||
<article v-for="stage in stages" :key="stage.key" class="panel production-pipeline-card">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex items-start gap-3">
|
||||
<span class="production-step"><component :is="stage.icon" :size="17" /></span>
|
||||
<div>
|
||||
<p class="eyebrow">STEP {{ stage.code }}</p>
|
||||
<h3 class="mt-2 text-sm font-semibold">{{ stage.title }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<span class="font-mono text-xs text-muted">{{ stage.done }} / {{ stage.data?.total ?? 0 }}</span>
|
||||
</div>
|
||||
<p class="mt-4 min-h-10 text-xs leading-5 text-muted">{{ stage.description }}</p>
|
||||
<ProgressRoot
|
||||
:model-value="stage.done"
|
||||
:max="Math.max(stage.data?.total ?? 0, 1)"
|
||||
:aria-label="`${stage.title}完成数量`"
|
||||
class="progress-track mt-4"
|
||||
>
|
||||
<ProgressIndicator
|
||||
class="progress-fill"
|
||||
:style="{
|
||||
width: `${stage.data?.total ? Math.min(100, (stage.done / stage.data.total) * 100) : 0}%`
|
||||
}"
|
||||
/>
|
||||
</ProgressRoot>
|
||||
<dl class="production-stats mt-4">
|
||||
<div>
|
||||
<dt>可执行</dt>
|
||||
<dd>{{ stage.data?.ready ?? 0 }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>已有结果</dt>
|
||||
<dd>{{ stage.data?.skipped ?? 0 }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>阻塞</dt>
|
||||
<dd>{{ stage.data?.blocked ?? 0 }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<p v-if="stage.key === 'keyframes' && staleKeyframes" class="mt-3 text-[11px] leading-5">
|
||||
已过期 {{ staleKeyframes }} · 会按当前主体参考资产重新生成
|
||||
</p>
|
||||
<p v-if="stage.key === 'videos' && staleVideos" class="mt-3 text-[11px] leading-5">
|
||||
主视频已过期 {{ staleVideos }} · 会按当前主首帧与参考资产重新生成
|
||||
</p>
|
||||
<p v-if="stage.key === 'keyframes' && identityBlocked" class="mt-3 text-[11px] leading-5 text-danger">
|
||||
身份缺失 {{ query.data.value?.keyframes.missingIdentity ?? 0 }} · 母版缺失
|
||||
{{ query.data.value?.keyframes.missingIdentityAnchor ?? 0 }} · 未锁定
|
||||
{{ query.data.value?.keyframes.identityUnlocked ?? 0 }}
|
||||
</p>
|
||||
<ConfirmAction
|
||||
class="mt-4"
|
||||
:label="stage.action"
|
||||
:disabled="blocked || !batchValid || !(stage.data?.ready ?? 0)"
|
||||
acknowledgement
|
||||
:description="
|
||||
stage.key === 'videos'
|
||||
? staleVideos
|
||||
? '当前存在过期主视频,非覆盖模式只会为这些镜头创建新的 Seedance 任务;新视频完成后会自动接替过期主视频。'
|
||||
: '只为当前就绪镜头创建 Seedance 异步任务。创建成功不代表视频已经完成,任务会在后台继续运行。'
|
||||
: stage.key === 'keyframes'
|
||||
? '处理缺失主首帧和参考资产已变化的过期主首帧。过期镜头会按当前主体参考资产重新生成并恢复为可用于视频的主首帧;覆盖模式只新增候选。'
|
||||
: '只处理通过视频提示词就绪检查的镜头;覆盖模式会重写已有提示词。'
|
||||
"
|
||||
:primary="stage.key === 'videos'"
|
||||
@confirm="run(stage.key)"
|
||||
/>
|
||||
</article>
|
||||
</div>
|
||||
<div v-if="query.data.value" class="panel mt-4 p-5">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<WorkspacePage split
|
||||
><template #header
|
||||
><div class="flex flex-wrap items-end justify-between gap-4">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold">视频任务状态</h3>
|
||||
<p class="mt-2 text-xs text-muted">状态统计取每个镜头最近一次任务,不等同于主视频数量。</p>
|
||||
<h2 class="text-lg font-semibold">镜头生产</h2>
|
||||
<p class="mt-2 text-sm text-muted">从可生成的提示词和首帧,到异步视频任务与最终成片。</p>
|
||||
</div>
|
||||
<ConfirmAction
|
||||
label="重试失败视频"
|
||||
:disabled="blocked || !batchValid || !query.data.value.videoStatus.failed"
|
||||
acknowledgement
|
||||
description="为当前最近一次状态为失败的镜头重新创建 Seedance 任务。成功镜头和进行中的镜头不会处理。"
|
||||
@confirm="run('retry-videos')"
|
||||
/>
|
||||
<RouterLink :to="`/projects/${id}/storyboard`" class="text-button">
|
||||
<ArrowLeft :size="14" />回到分镜设计
|
||||
</RouterLink>
|
||||
</div>
|
||||
<dl class="production-status-grid mt-4">
|
||||
<div><dt>完成</dt><dd>{{ query.data.value.videoStatus.completed }}</dd></div>
|
||||
<div><dt>排队/生成</dt><dd>{{ videoRunning }}</dd></div>
|
||||
<div><dt>失败</dt><dd>{{ query.data.value.videoStatus.failed }}</dd></div>
|
||||
<div><dt>取消</dt><dd>{{ query.data.value.videoStatus.cancelled }}</dd></div>
|
||||
<div><dt>未开始</dt><dd>{{ query.data.value.videoStatus.notStarted }}</dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<section class="mt-3" aria-label="项目生产配置">
|
||||
<div class="production-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"
|
||||
: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>
|
||||
<NCheckbox
|
||||
v-model:checked="force"
|
||||
:disabled="operation.pending"
|
||||
class="flex items-center gap-2 pb-3 text-xs"
|
||||
>覆盖模式:为已有结果新增候选
|
||||
</NCheckbox>
|
||||
<NButton
|
||||
:disabled="query.loading.value"
|
||||
@click="query.refresh"
|
||||
text
|
||||
size="small"
|
||||
class="mb-2 ml-auto"
|
||||
><RefreshCw :size="13" :class="{ 'animate-spin': query.loading.value }" />刷新就绪状态
|
||||
</NButton>
|
||||
</div>
|
||||
<p class="mt-3 text-[11px] leading-6 text-muted">
|
||||
提示词与视频批量操作面向全项目;首帧默认补当前剧集,存在过期主首帧时优先更新全项目过期项,覆盖模式新增全项目候选。Seedream
|
||||
与 Seedance 均可能产生费用。
|
||||
</p>
|
||||
</section></template
|
||||
>
|
||||
<NAlert v-if="query.error.value" role="alert" type="error" :show-icon="false" class="mt-4"
|
||||
>{{ query.error.value
|
||||
}}<span v-if="query.data.value"> 当前保留上次读取的数据,生产操作已暂停。</span></NAlert
|
||||
>
|
||||
<NCollapse v-model:expanded-names="overview" class="workspace-overview"
|
||||
><NCollapseItem name="overview" title="项目生产总览与批量操作"
|
||||
><div class="overview-content">
|
||||
<NAlert type="info" :show-icon="false" class="mt-5 text-xs">
|
||||
每一步都按后端就绪检查执行。覆盖只会新增首帧/视频候选,不会替换当前主资产;上游参考资产变化后,下游过期结果会被标记并阻止继续使用,需要重新生成后才能恢复就绪。
|
||||
</NAlert>
|
||||
<NAlert v-if="videoRunning" role="status" type="info" :show-icon="false" class="mt-4">
|
||||
当前有 {{ videoRunning }} 个视频任务等待或生成中。后端正在轮询
|
||||
Provider;同一镜头不会重复提交活动任务。
|
||||
</NAlert>
|
||||
<NAlert v-if="identityBlocked" type="info" :show-icon="false" class="mt-4">
|
||||
当前有 {{ identityBlocked }} 项角色身份前置问题。Character 必须生成
|
||||
Identity、确认演员母版并锁定后,首帧才能就绪。
|
||||
<RouterLink :to="`/projects/${id}/subject-identity`" class="text-button ml-2"
|
||||
>前往角色选角 →</RouterLink
|
||||
></NAlert
|
||||
>
|
||||
<NAlert v-if="staleKeyframes" role="status" type="info" :show-icon="false" class="mt-4">
|
||||
当前有
|
||||
{{ staleKeyframes }}
|
||||
个主首帧已过期。它们使用的主体参考资产已经变化,视频阶段会被阻止;可在下方逐镜重新生成,或使用“补齐
|
||||
/ 更新主首帧”批量处理。
|
||||
</NAlert>
|
||||
<NAlert v-if="staleVideos" role="status" type="info" :show-icon="false" class="mt-4">
|
||||
当前有
|
||||
{{ staleVideos }}
|
||||
个主视频已过期。它们使用的主首帧或主体参考图已经变化;非覆盖模式批量生成时会只重建这些过期主视频。
|
||||
</NAlert>
|
||||
<ProductionReceipt v-if="session.receipt" :receipt="session.receipt" />
|
||||
<div v-if="query.data.value" class="production-pipeline mt-5">
|
||||
<article v-for="stage in stages" :key="stage.key" class="panel production-pipeline-card">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex items-start gap-3">
|
||||
<span class="production-step"><component :is="stage.icon" :size="17" /></span>
|
||||
<div>
|
||||
<p class="eyebrow">STEP {{ stage.code }}</p>
|
||||
<h3 class="mt-2 text-sm font-semibold">{{ stage.title }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<span class="font-mono text-xs text-muted"
|
||||
>{{ stage.done }} / {{ stage.data?.total ?? 0 }}</span
|
||||
>
|
||||
</div>
|
||||
<p class="mt-4 min-h-10 text-xs leading-5 text-muted">{{ stage.description }}</p>
|
||||
<NProgress
|
||||
:aria-label="`${stage.title}完成数量`"
|
||||
type="line"
|
||||
:show-indicator="false"
|
||||
:height="4"
|
||||
:percentage="
|
||||
Math.min(
|
||||
100,
|
||||
Math.max(
|
||||
0,
|
||||
(stage.done / Math.max(1, Math.max(stage.data?.total ?? 0, 1))) * 100
|
||||
)
|
||||
)
|
||||
"
|
||||
class="mt-4"
|
||||
></NProgress>
|
||||
<dl class="production-stats mt-4">
|
||||
<div>
|
||||
<dt>可执行</dt>
|
||||
<dd>{{ stage.data?.ready ?? 0 }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>已有结果</dt>
|
||||
<dd>{{ stage.data?.skipped ?? 0 }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>阻塞</dt>
|
||||
<dd>{{ stage.data?.blocked ?? 0 }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<p v-if="stage.key === 'keyframes' && staleKeyframes" class="mt-3 text-[11px] leading-5">
|
||||
已过期 {{ staleKeyframes }} · 会按当前主体参考资产重新生成
|
||||
</p>
|
||||
<p v-if="stage.key === 'videos' && staleVideos" class="mt-3 text-[11px] leading-5">
|
||||
主视频已过期 {{ staleVideos }} · 会按当前主首帧与参考资产重新生成
|
||||
</p>
|
||||
<p
|
||||
v-if="stage.key === 'keyframes' && identityBlocked"
|
||||
class="mt-3 text-[11px] leading-5 text-danger"
|
||||
>
|
||||
身份缺失 {{ query.data.value?.keyframes.missingIdentity ?? 0 }} · 母版缺失
|
||||
{{ query.data.value?.keyframes.missingIdentityAnchor ?? 0 }} · 未锁定
|
||||
{{ query.data.value?.keyframes.identityUnlocked ?? 0 }}
|
||||
</p>
|
||||
<ConfirmAction
|
||||
class="mt-4"
|
||||
:label="stage.action"
|
||||
:disabled="blocked || !batchValid || !(stage.data?.ready ?? 0)"
|
||||
acknowledgement
|
||||
:description="
|
||||
stage.key === 'videos'
|
||||
? staleVideos && !force
|
||||
? '当前存在过期主视频,非覆盖模式只会为这些镜头创建新的 Seedance 任务;新视频完成后会自动接替过期主视频。'
|
||||
: '只为当前就绪镜头创建 Seedance 异步任务。创建成功不代表视频已经完成,任务会在后台继续运行。'
|
||||
: stage.key === 'keyframes'
|
||||
? '处理缺失主首帧和参考资产已变化的过期主首帧。过期镜头会按当前主体参考资产重新生成并恢复为可用于视频的主首帧;覆盖模式只新增候选。'
|
||||
: '只处理通过视频提示词就绪检查的镜头;覆盖模式会重写已有提示词。'
|
||||
"
|
||||
:primary="stage.key === 'videos'"
|
||||
@confirm="run(stage.key)"
|
||||
/>
|
||||
</article>
|
||||
</div>
|
||||
<div v-if="query.data.value" class="panel mt-4 p-5">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold">视频任务状态</h3>
|
||||
<p class="mt-2 text-xs text-muted">
|
||||
状态统计取每个镜头最近一次任务,不等同于主视频数量。
|
||||
</p>
|
||||
</div>
|
||||
<ConfirmAction
|
||||
label="重试失败视频"
|
||||
:disabled="blocked || !batchValid || !query.data.value.videoStatus.failed"
|
||||
acknowledgement
|
||||
description="为当前最近一次状态为失败的镜头重新创建 Seedance 任务。成功镜头和进行中的镜头不会处理。"
|
||||
@confirm="run('retry-videos')"
|
||||
/>
|
||||
</div>
|
||||
<dl class="production-status-grid mt-4">
|
||||
<div>
|
||||
<dt>完成</dt>
|
||||
<dd>{{ query.data.value.videoStatus.completed }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>排队/生成</dt>
|
||||
<dd>{{ videoRunning }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>失败</dt>
|
||||
<dd>{{ query.data.value.videoStatus.failed }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>取消</dt>
|
||||
<dd>{{ query.data.value.videoStatus.cancelled }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>未开始</dt>
|
||||
<dd>{{ query.data.value.videoStatus.notStarted }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div></NCollapseItem
|
||||
></NCollapse
|
||||
>
|
||||
<div class="my-6 flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold">第 {{ episodeNo || '—' }} 集 · 逐镜生产</h3>
|
||||
@@ -276,35 +328,43 @@ function refreshProject() {
|
||||
</div>
|
||||
<div v-if="shot" class="panel production-workspace">
|
||||
<nav class="production-shot-list" aria-label="本集生产镜头">
|
||||
<button
|
||||
<NButton
|
||||
v-for="item in shots"
|
||||
:key="item.shotId"
|
||||
class="production-shot-link"
|
||||
:class="{ selected: item.shotId === shot.shotId }"
|
||||
:aria-current="item.shotId === shot.shotId ? 'true' : undefined"
|
||||
@click="selectedShot = item.shotId"
|
||||
>
|
||||
<span class="font-mono text-[10px] text-muted">B{{ item.beatNo }} / S{{ item.shotNo }}</span>
|
||||
<span class="mt-1 block truncate text-xs font-medium">{{ item.title }}</span>
|
||||
<span
|
||||
v-if="query.data.value?.keyframes.items.find(row => row.shotId === item.shotId)?.primaryKeyframeStale"
|
||||
class="production-shot-link"
|
||||
:class="{ selected: item.shotId === shot.shotId }"
|
||||
><span class="font-mono text-[10px] text-muted">B{{ item.beatNo }} / S{{ item.shotNo }}</span
|
||||
><span class="mt-1 block truncate text-xs font-medium">{{ item.title }}</span
|
||||
><span
|
||||
v-if="
|
||||
query.data.value?.keyframes.items.find(row => row.shotId === item.shotId)
|
||||
?.primaryKeyframeStale
|
||||
"
|
||||
class="mt-2 flex items-center gap-2 text-[10px]"
|
||||
>
|
||||
<CircleAlert :size="12" />主首帧已过期
|
||||
</span>
|
||||
<span
|
||||
v-else-if="query.data.value?.videos.items.find(row => row.shotId === item.shotId)?.primaryVideoStale"
|
||||
<CircleAlert :size="12" />主首帧已过期 </span
|
||||
><span
|
||||
v-else-if="
|
||||
query.data.value?.videos.items.find(row => row.shotId === item.shotId)?.primaryVideoStale
|
||||
"
|
||||
class="mt-2 flex items-center gap-2 text-[10px]"
|
||||
>
|
||||
<CircleAlert :size="12" />主视频已过期
|
||||
</span>
|
||||
<span v-else class="mt-2 flex items-center gap-2 text-[10px] text-muted">
|
||||
<CircleAlert :size="12" />主视频已过期 </span
|
||||
><span v-else class="mt-2 flex items-center gap-2 text-[10px] text-muted">
|
||||
<CheckCircle2
|
||||
v-if="query.data.value?.videos.items.find(row => row.shotId === item.shotId)?.status === 'skipped'"
|
||||
v-if="
|
||||
query.data.value?.videos.items.find(row => row.shotId === item.shotId)?.status ===
|
||||
'skipped'
|
||||
"
|
||||
:size="12"
|
||||
/>
|
||||
<CircleAlert
|
||||
v-else-if="query.data.value?.videos.items.find(row => row.shotId === item.shotId)?.status === 'blocked'"
|
||||
v-else-if="
|
||||
query.data.value?.videos.items.find(row => row.shotId === item.shotId)?.status ===
|
||||
'blocked'
|
||||
"
|
||||
:size="12"
|
||||
/>
|
||||
{{
|
||||
@@ -313,8 +373,8 @@ function refreshProject() {
|
||||
'not_started'
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</button>
|
||||
</span></NButton
|
||||
>
|
||||
</nav>
|
||||
<article class="min-w-0 p-5 lg:p-7">
|
||||
<header class="mb-5 border-b border-line pb-5">
|
||||
@@ -331,12 +391,24 @@ function refreshProject() {
|
||||
</div>
|
||||
<p v-if="shot.description" class="mt-3 text-sm leading-7">{{ shot.description }}</p>
|
||||
<p class="mt-3 break-all font-mono text-[10px] text-muted">Shot ID · {{ shot.shotId }}</p>
|
||||
<p v-if="keyframeItem?.primaryKeyframeStale" class="alert mt-4 text-xs" role="status">
|
||||
<NAlert
|
||||
v-if="keyframeItem?.primaryKeyframeStale"
|
||||
role="status"
|
||||
type="info"
|
||||
:show-icon="false"
|
||||
class="mt-4 text-xs"
|
||||
>
|
||||
主首帧已过期:当前主体参考资产与生成该首帧时不一致。旧首帧会保留,但不能继续用于视频生成。
|
||||
</p>
|
||||
<p v-if="videoItem?.primaryVideoStale" class="alert mt-4 text-xs" role="status">
|
||||
</NAlert>
|
||||
<NAlert
|
||||
v-if="videoItem?.primaryVideoStale"
|
||||
role="status"
|
||||
type="info"
|
||||
:show-icon="false"
|
||||
class="mt-4 text-xs"
|
||||
>
|
||||
主视频已过期:当前主首帧或主体参考图与生成该视频时不一致。旧视频仍保留,但需要重新生成后才能作为当前有效成片。
|
||||
</p>
|
||||
</NAlert>
|
||||
<ul v-if="selectedIssues.length" class="alert alert-error mt-4 space-y-1" role="alert">
|
||||
<li v-for="(issue, index) in selectedIssues" :key="`${issue.code}-${index}`">
|
||||
{{ issueLabel(issue.code) }}:{{ issue.reason }}
|
||||
@@ -364,6 +436,6 @@ function refreshProject() {
|
||||
</EmptyState>
|
||||
<p v-else-if="query.loading.value" class="panel mt-5 p-8 text-sm text-muted" role="status">
|
||||
正在检查全项目生产就绪状态……
|
||||
</p>
|
||||
</section>
|
||||
</template>
|
||||
</p></WorkspacePage
|
||||
>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user