refactor: 使用 Naive UI 重构后台布局与黑白双主题

This commit is contained in:
GouJ
2026-09-01 16:31:21 +08:00
parent 55516537bf
commit db8c176b55
51 changed files with 3277 additions and 2442 deletions
+63 -57
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import WorkspacePage from '../../components/ui/WorkspacePage.vue'
import { NButton, NCollapse, NCollapseItem, NProgress, NTab, NTabs, NTag } from 'naive-ui'
import { computed, ref } from 'vue'
import { TabsRoot, TabsList, TabsTrigger, TabsContent, ProgressRoot, ProgressIndicator } from 'reka-ui'
import { ArrowRight, Download, Check, Circle } from '@lucide/vue'
import { EmptyState } from '../../components/ui'
import { useProjectContext } from '../projects/context'
@@ -67,13 +68,19 @@ function exportScript() {
</script>
<template>
<div class="mt-6">
<div class="stage-strip">
<div v-for="(stage, index) in stages" :key="stage.label" class="stage-item" :class="{ done: stage.done }">
<Check v-if="stage.done" :size="15" /><Circle v-else :size="13" /><span>{{ stage.label }}</span
><ArrowRight v-if="index < stages.length - 1" :size="13" class="stage-arrow" />
</div>
</div>
<WorkspacePage split class="script-workspace-page"
><template #header
><div class="stage-strip">
<div
v-for="(stage, index) in stages"
:key="stage.label"
class="stage-item"
:class="{ done: stage.done }"
>
<Check v-if="stage.done" :size="15" /><Circle v-else :size="13" /><span>{{ stage.label }}</span
><ArrowRight v-if="index < stages.length - 1" :size="13" class="stage-arrow" />
</div></div
></template>
<div v-if="!complete" class="mt-5 flex flex-wrap items-center justify-between gap-4">
<p class="text-sm text-muted">
已写入 {{ episodes.length }}<span v-if="total"> / {{ total }}</span> 集<span v-if="!total">
@@ -98,28 +105,26 @@ function exportScript() {
/>
</div>
</div>
<ProgressRoot
<NProgress
v-if="total && !complete"
class="progress-track mt-4"
:model-value="Math.min(episodes.length, total)"
:max="total"
aria-label="已写入剧集数量"
><ProgressIndicator
class="progress-fill"
:style="{ width: Math.min(100, (episodes.length / total) * 100) + '%' }"
/></ProgressRoot>
<TabsRoot v-model="tab" class="mt-7">
type="line"
:show-indicator="false"
:height="4"
:percentage="Math.min(100, Math.max(0, (Math.min(episodes.length, total) / Math.max(1, total)) * 100))"
class="mt-4"
></NProgress>
<div class="script-section">
<div class="flex flex-wrap items-center justify-between gap-3">
<TabsList class="tabs-list" aria-label="剧本内容"
><TabsTrigger class="tab-trigger" value="episodes"
>剧集正文 <span>{{ episodes.length }}</span></TabsTrigger
><TabsTrigger class="tab-trigger" value="characters">角色设定</TabsTrigger
><TabsTrigger class="tab-trigger" value="world">世界观</TabsTrigger
><TabsTrigger class="tab-trigger" value="review">审核记录</TabsTrigger></TabsList
<NTabs v-model:value="tab" type="line" size="small" aria-label="剧本内容"
><NTab name="episodes"
>剧集正文 <span>{{ episodes.length }}</span></NTab
><NTab name="characters">角色设定</NTab><NTab name="world">世界观</NTab
><NTab name="review">审核记录</NTab></NTabs
>
<div class="flex items-center gap-3">
<button class="text-button" :disabled="!episodes.length" @click="exportScript">
<Download :size="14" />导出剧本</button
<NButton :disabled="!episodes.length" @click="exportScript" text size="small"
><Download :size="14" />导出剧本</NButton
><RouterLink
v-if="complete"
class="text-button text-accent"
@@ -130,21 +135,20 @@ function exportScript() {
</div>
<div class="content-with-history panel mt-4">
<main class="min-w-0">
<TabsContent value="episodes" class="h-full">
<div v-if="tab === 'episodes'" class="h-full">
<div v-if="episode" class="script-workspace">
<aside class="episode-list">
<p class="px-4 pb-3 pt-5 text-[11px] font-medium tracking-wider text-muted">剧集目录</p>
<button
<NButton
v-for="item in episodes"
:key="item.episode"
class="episode-link"
:class="{ active: episode.episode === item.episode }"
:aria-pressed="episode.episode === item.episode"
@click="selected = item.episode"
class="episode-link"
:class="{ active: episode.episode === item.episode }"
><span class="episode-number">{{ String(item.episode).padStart(2, '0') }}</span
><span class="truncate">{{ item.title }}</span></NButton
>
<span class="episode-number">{{ String(item.episode).padStart(2, '0') }}</span
><span class="truncate">{{ item.title }}</span>
</button>
</aside>
<article class="script-page">
<p class="eyebrow"> {{ String(episode.episode).padStart(2, '0') }} </p>
@@ -167,9 +171,9 @@ function exportScript() {
title="剧本正在酝酿"
description="剧集写入数据库后会自动出现在这里。角色与世界观生成期间,可以在右侧查看已保存的执行记录。"
/>
</TabsContent>
<TabsContent value="characters" class="p-6 lg:p-8"
><div v-if="project?.characters.length" class="divide-y divide-line">
</div>
<div v-if="tab === 'characters'" class="p-6 lg:p-8">
<div v-if="project?.characters.length" class="divide-y divide-line">
<article
v-for="character in project.characters"
:key="character.id"
@@ -177,7 +181,7 @@ function exportScript() {
>
<div class="mb-3 flex items-center gap-3">
<h3 class="text-lg font-semibold">{{ character.name }}</h3>
<span class="tag">{{ character.role || '角色' }}</span
<NTag size="small" :bordered="false">{{ character.role || '角色' }}</NTag
><span class="text-xs text-muted">{{ character.occupation }}</span>
</div>
<dl class="detail-grid">
@@ -194,10 +198,10 @@ function exportScript() {
</dl>
</article>
</div>
<EmptyState v-else title="尚无角色设定" description="角色生成结束后会在这里显示。"
/></TabsContent>
<TabsContent value="world" class="p-6 lg:p-8"
><div v-if="project?.world">
<EmptyState v-else title="尚无角色设定" description="角色生成结束后会在这里显示。" />
</div>
<div v-if="tab === 'world'" class="p-6 lg:p-8">
<div v-if="project?.world">
<p class="eyebrow">故事的发生之地</p>
<h2 class="mb-7 mt-3 text-xl font-semibold">
{{ project.world.era }} · {{ project.world.location }}
@@ -214,15 +218,17 @@ function exportScript() {
<dd>{{ project.world[field.key] || '未提供' }}</dd></template
>
</dl>
<details v-if="project.world.rules" class="mt-6 text-sm">
<summary class="cursor-pointer text-muted">世界规则</summary>
<pre class="json-view mt-3">{{ JSON.stringify(project.world.rules, null, 2) }}</pre>
</details>
<NCollapse v-if="project.world.rules" class="mt-6 text-sm"
><NCollapseItem name="details"
><template #header>世界规则</template>
<pre class="json-view mt-3">{{ JSON.stringify(project.world.rules, null, 2) }}</pre>
</NCollapseItem></NCollapse
>
</div>
<EmptyState v-else title="尚无世界观" description="世界观会在角色设定之后生成。"
/></TabsContent>
<TabsContent value="review" class="p-6 lg:p-8"
><div v-if="project?.reviews.length" class="space-y-5">
<EmptyState v-else title="尚无世界观" description="世界观会在角色设定之后生成。" />
</div>
<div v-if="tab === 'review'" class="p-6 lg:p-8">
<div v-if="project?.reviews.length" class="space-y-5">
<article
v-for="review in project.reviews"
:key="review.id"
@@ -244,15 +250,15 @@ function exportScript() {
v-else
title="还没有审核记录"
description="剧集生成完成后,工作流会进行内容审核与必要的改写。"
/>
<details v-if="state?.rewriteSuggestion" class="mt-5 text-sm">
<summary class="cursor-pointer text-muted">查看改写建议原文</summary>
<pre class="json-view mt-3">{{ JSON.stringify(state.rewriteSuggestion, null, 2) }}</pre>
</details></TabsContent
>
/><NCollapse v-if="state?.rewriteSuggestion" class="mt-5 text-sm"
><NCollapseItem name="details"
><template #header>查看改写建议原文</template>
<pre class="json-view mt-3">{{ JSON.stringify(state.rewriteSuggestion, null, 2) }}</pre>
</NCollapseItem></NCollapse
>
</div>
</main>
<HistoryPanel :checkpoints="checkpoints" workflow="create-drama" />
</div>
</TabsRoot>
</div>
</div></div
></WorkspacePage>
</template>