refactor: 使用 Naive UI 重构后台布局与黑白双主题
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { NAlert, NButton, NCollapse, NCollapseItem, NTag } from 'naive-ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { ExternalLink, FileSearch, ImagePlus, RefreshCw } from '@lucide/vue'
|
||||
import { AssetImage, StatusBadge } from '../../../components/ui'
|
||||
@@ -189,18 +190,18 @@ async function inspectSpecs() {
|
||||
<p class="mt-1 text-[11px] text-muted">记录每次候选与失败,主资产决定下一阶段实际输入。</p>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<button class="text-button" :disabled="inspecting" @click="inspectSpecs">
|
||||
<FileSearch :size="13" />{{ inspecting ? '检查中…' : '检查生成规格' }}
|
||||
</button>
|
||||
<button class="text-button" :disabled="query.loading.value" @click="refreshAll">
|
||||
<RefreshCw :size="13" :class="{ 'animate-spin': query.loading.value }" />刷新资产
|
||||
</button>
|
||||
<NButton :disabled="inspecting" @click="inspectSpecs" text size="small"
|
||||
><FileSearch :size="13" />{{ inspecting ? '检查中…' : '检查生成规格' }}</NButton
|
||||
>
|
||||
<NButton :disabled="query.loading.value" @click="refreshAll" text size="small"
|
||||
><RefreshCw :size="13" :class="{ 'animate-spin': query.loading.value }" />刷新资产
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="query.error.value" class="alert alert-error mt-4" role="alert">
|
||||
{{ query.error.value }} 当前保留上次成功读取的资产,操作已暂停。
|
||||
</p>
|
||||
<p v-if="specError" class="alert alert-error mt-4" role="alert">{{ specError }}</p>
|
||||
<NAlert v-if="query.error.value" role="alert" type="error" :show-icon="false" class="mt-4"
|
||||
>{{ query.error.value }} 当前保留上次成功读取的资产,操作已暂停。
|
||||
</NAlert>
|
||||
<NAlert v-if="specError" role="alert" type="error" :show-icon="false" class="mt-4">{{ specError }}</NAlert>
|
||||
|
||||
<div class="production-stage mt-5">
|
||||
<div class="production-stage-heading">
|
||||
@@ -246,9 +247,9 @@ async function inspectSpecs() {
|
||||
:label="keyframeStale ? '主首帧已过期' : productionStatusLabel(keyframeReadiness.status)"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="keyframeStale" class="alert mt-3 text-xs" role="status">
|
||||
<NAlert v-if="keyframeStale" role="status" type="info" :show-icon="false" class="mt-3 text-xs">
|
||||
当前主首帧使用的主体参考资产已经变化,旧图仍保留但不会继续进入视频生成。请重新生成主首帧。
|
||||
</p>
|
||||
</NAlert>
|
||||
<ul v-if="keyframeReadiness?.issues.length" class="mt-3 space-y-2 text-xs text-danger">
|
||||
<li v-for="issue in keyframeReadiness.issues" :key="issue.code">
|
||||
{{ issueLabel(issue.code) }}:{{ issue.reason }}
|
||||
@@ -264,34 +265,44 @@ async function inspectSpecs() {
|
||||
<div class="p-3">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<StatusBadge :status="item.status" :label="productionStatusLabel(item.status)" />
|
||||
<span v-if="item.isPrimary" class="tag">主首帧</span>
|
||||
<span v-if="item.isPrimary && keyframeStale" class="tag">已过期</span>
|
||||
<span class="text-[10px] text-muted">{{ item.width || '—' }} × {{ item.height || '—' }}</span>
|
||||
<NTag v-if="item.isPrimary" size="small" :bordered="false">主首帧</NTag>
|
||||
<NTag v-if="item.isPrimary && keyframeStale" size="small" :bordered="false">已过期</NTag>
|
||||
<span class="text-[10px] text-muted"
|
||||
>{{ item.width || '—' }} × {{ item.height || '—' }}</span
|
||||
>
|
||||
</div>
|
||||
<p v-if="item.error" class="mt-2 line-clamp-3 text-xs text-danger">{{ item.error }}</p>
|
||||
<p class="mt-2 text-[10px] text-muted">{{ formatDate(item.createdAt) }}</p>
|
||||
<button
|
||||
<NButton
|
||||
v-if="item.status === 'completed' && item.imageUrl && !item.isPrimary"
|
||||
class="text-button mt-2"
|
||||
:disabled="assetBlocked"
|
||||
@click="confirmingKeyframe = item.id"
|
||||
text
|
||||
size="small"
|
||||
class="mt-2"
|
||||
>
|
||||
设为主首帧
|
||||
</button>
|
||||
<div v-if="confirmingKeyframe === item.id" class="alert mt-2 text-xs">
|
||||
<p>确认让后续视频使用此首帧?已有视频不会自动重生成。</p>
|
||||
</NButton>
|
||||
<NAlert
|
||||
v-if="confirmingKeyframe === item.id"
|
||||
type="info"
|
||||
:show-icon="false"
|
||||
class="mt-2 text-xs"
|
||||
><p>确认让后续视频使用此首帧?已有视频不会自动重生成。</p>
|
||||
<div class="mt-2 flex gap-3">
|
||||
<button class="button button-primary" @click="setPrimaryKeyframe">确认切换</button>
|
||||
<button class="text-button" @click="confirmingKeyframe = ''">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
<NButton @click="setPrimaryKeyframe" type="primary">确认切换</NButton>
|
||||
<NButton @click="confirmingKeyframe = ''" text size="small">取消</NButton>
|
||||
</div></NAlert
|
||||
>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<p v-else class="mt-4 text-xs text-muted">尚无首帧记录。</p>
|
||||
<button class="button button-secondary mt-4" :disabled="!canGenerateKeyframe" @click="keyframeOpen = true">
|
||||
<ImagePlus :size="14" />{{ keyframeStale ? '重新生成主首帧' : currentKeyframe ? '再生成一张候选' : '生成首帧' }}
|
||||
</button>
|
||||
<NButton :disabled="!canGenerateKeyframe" @click="keyframeOpen = true" class="mt-4"
|
||||
><ImagePlus :size="14" />{{
|
||||
keyframeStale ? '重新生成主首帧' : currentKeyframe ? '再生成一张候选' : '生成首帧'
|
||||
}}</NButton
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="production-stage">
|
||||
@@ -306,9 +317,9 @@ async function inspectSpecs() {
|
||||
:label="videoStale ? '主视频已过期' : productionStatusLabel(videoReadiness.status)"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="videoStale" class="alert mt-3 text-xs" role="status">
|
||||
<NAlert v-if="videoStale" role="status" type="info" :show-icon="false" class="mt-3 text-xs">
|
||||
当前主视频使用的主首帧或主体参考图已经变化。旧视频仍保留;重新生成完成后,新视频会自动接替过期主视频。
|
||||
</p>
|
||||
</NAlert>
|
||||
<ul v-if="videoReadiness?.issues.length" class="mt-3 space-y-2 text-xs text-danger">
|
||||
<li v-for="issue in videoReadiness.issues" :key="issue.code">
|
||||
{{ issueLabel(issue.code) }}:{{ issue.reason }}
|
||||
@@ -326,11 +337,15 @@ async function inspectSpecs() {
|
||||
<div class="min-w-0 flex-1 p-4">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<StatusBadge :status="item.status" :label="productionStatusLabel(item.status)" />
|
||||
<span v-if="item.isPrimary" class="tag">主视频</span>
|
||||
<span v-if="item.isPrimary && videoStale" class="tag">已过期</span>
|
||||
<span class="text-[10px] text-muted">{{ item.durationSeconds || shot.durationSeconds || '—' }}s</span>
|
||||
<NTag v-if="item.isPrimary" size="small" :bordered="false">主视频</NTag>
|
||||
<NTag v-if="item.isPrimary && videoStale" size="small" :bordered="false">已过期</NTag>
|
||||
<span class="text-[10px] text-muted"
|
||||
>{{ item.durationSeconds || shot.durationSeconds || '—' }}s</span
|
||||
>
|
||||
</div>
|
||||
<p v-if="item.error" class="alert alert-error mt-3" role="alert">{{ item.error }}</p>
|
||||
<NAlert v-if="item.error" role="alert" type="error" :show-icon="false" class="mt-3">{{
|
||||
item.error
|
||||
}}</NAlert>
|
||||
<p class="mt-3 break-all text-[10px] text-muted">
|
||||
{{ item.provider }} · {{ item.model }} · {{ formatDate(item.createdAt) }}
|
||||
</p>
|
||||
@@ -341,24 +356,25 @@ async function inspectSpecs() {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-button"
|
||||
>打开视频<ExternalLink :size="12" /></a
|
||||
>
|
||||
<button
|
||||
>打开视频<ExternalLink :size="12"
|
||||
/></a>
|
||||
<NButton
|
||||
v-if="item.status === 'completed' && item.videoUrl && !item.isPrimary"
|
||||
class="text-button"
|
||||
:disabled="assetBlocked"
|
||||
@click="confirmingVideo = item.id"
|
||||
text
|
||||
size="small"
|
||||
>
|
||||
设为主视频
|
||||
</button>
|
||||
</NButton>
|
||||
</div>
|
||||
<div v-if="confirmingVideo === item.id" class="alert mt-3 text-xs">
|
||||
<p>确认把此候选设为当前镜头主视频?旧成片仍会保留。</p>
|
||||
<NAlert v-if="confirmingVideo === item.id" type="info" :show-icon="false" class="mt-3 text-xs"
|
||||
><p>确认把此候选设为当前镜头主视频?旧成片仍会保留。</p>
|
||||
<div class="mt-2 flex gap-3">
|
||||
<button class="button button-primary" @click="setPrimaryVideo">确认切换</button>
|
||||
<button class="text-button" @click="confirmingVideo = ''">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
<NButton @click="setPrimaryVideo" type="primary">确认切换</NButton>
|
||||
<NButton @click="confirmingVideo = ''" text size="small">取消</NButton>
|
||||
</div></NAlert
|
||||
>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
@@ -378,46 +394,43 @@ async function inspectSpecs() {
|
||||
primary
|
||||
@confirm="generateVideo"
|
||||
/>
|
||||
<button
|
||||
v-if="activeVideo"
|
||||
class="button button-secondary"
|
||||
:disabled="assetBlocked"
|
||||
@click="refreshActiveVideo"
|
||||
>
|
||||
<RefreshCw :size="14" />刷新任务状态
|
||||
</button>
|
||||
<NButton v-if="activeVideo" :disabled="assetBlocked" @click="refreshActiveVideo"
|
||||
><RefreshCw :size="14" />刷新任务状态
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details v-if="keyframeSpec || videoSpec" class="mt-5 border-t border-line pt-4 text-xs">
|
||||
<summary class="cursor-pointer font-medium">本镜确定性生成规格</summary>
|
||||
<div class="mt-4 grid gap-5 lg:grid-cols-2">
|
||||
<div v-if="keyframeSpec">
|
||||
<h5 class="font-medium">首帧 Prompt · {{ keyframeSpec.references.length }} 张参考图</h5>
|
||||
<p class="mt-3 whitespace-pre-wrap leading-6 text-muted">{{ keyframeSpec.prompt }}</p>
|
||||
<ul v-if="keyframeSpec.references.length" class="mt-3 space-y-1 text-[11px] text-muted">
|
||||
<li
|
||||
v-for="reference in keyframeSpec.references"
|
||||
:key="`${reference.role}:${reference.imageId}`"
|
||||
>
|
||||
{{ reference.subjectRef }} ·
|
||||
{{ reference.role === 'identity-anchor' ? '演员身份母版' : '形态主图' }} ·
|
||||
<code>{{ reference.imageId }}</code>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="videoSpec">
|
||||
<h5 class="font-medium">视频 Prompt · {{ videoSpec.durationSeconds }}s</h5>
|
||||
<p class="mt-3 whitespace-pre-wrap leading-6 text-muted">{{ videoSpec.videoPrompt }}</p>
|
||||
<p v-if="videoSpec.negativePrompt" class="mt-3 whitespace-pre-wrap leading-6 text-muted">
|
||||
Negative prompt: {{ videoSpec.negativePrompt }}
|
||||
</p>
|
||||
<p class="mt-3 break-all font-mono text-[10px] text-muted">
|
||||
Keyframe ID · {{ videoSpec.keyframe.id }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<NCollapse v-if="keyframeSpec || videoSpec" class="mt-5 border-t border-line pt-4 text-xs"
|
||||
><NCollapseItem name="details"
|
||||
><template #header>本镜确定性生成规格</template>
|
||||
<div class="mt-4 grid gap-5 lg:grid-cols-2">
|
||||
<div v-if="keyframeSpec">
|
||||
<h5 class="font-medium">首帧 Prompt · {{ keyframeSpec.references.length }} 张参考图</h5>
|
||||
<p class="mt-3 whitespace-pre-wrap leading-6 text-muted">{{ keyframeSpec.prompt }}</p>
|
||||
<ul v-if="keyframeSpec.references.length" class="mt-3 space-y-1 text-[11px] text-muted">
|
||||
<li
|
||||
v-for="reference in keyframeSpec.references"
|
||||
:key="`${reference.role}:${reference.imageId}`"
|
||||
>
|
||||
{{ reference.subjectRef }} ·
|
||||
{{ reference.role === 'identity-anchor' ? '演员身份母版' : '形态主图' }} ·
|
||||
<code>{{ reference.imageId }}</code>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="videoSpec">
|
||||
<h5 class="font-medium">视频 Prompt · {{ videoSpec.durationSeconds }}s</h5>
|
||||
<p class="mt-3 whitespace-pre-wrap leading-6 text-muted">{{ videoSpec.videoPrompt }}</p>
|
||||
<p v-if="videoSpec.negativePrompt" class="mt-3 whitespace-pre-wrap leading-6 text-muted">
|
||||
Negative prompt: {{ videoSpec.negativePrompt }}
|
||||
</p>
|
||||
<p class="mt-3 break-all font-mono text-[10px] text-muted">
|
||||
Keyframe ID · {{ videoSpec.keyframe.id }}
|
||||
</p>
|
||||
</div>
|
||||
</div></NCollapseItem
|
||||
></NCollapse
|
||||
>
|
||||
<KeyframeDialog
|
||||
v-model:open="keyframeOpen"
|
||||
:shot-id="shot.shotId"
|
||||
@@ -428,4 +441,4 @@ async function inspectSpecs() {
|
||||
@generate="generateKeyframe"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user