161 lines
5.9 KiB
Vue
161 lines
5.9 KiB
Vue
<script setup lang="ts">
|
||
import { computed, h, ref } from 'vue'
|
||
import { RouterLink, useRoute } from 'vue-router'
|
||
import {
|
||
NButton,
|
||
NConfigProvider,
|
||
NGlobalStyle,
|
||
NLayout,
|
||
NLayoutHeader,
|
||
NLayoutSider,
|
||
NMenu,
|
||
NSelect,
|
||
NTooltip,
|
||
zhCN,
|
||
dateZhCN,
|
||
type MenuOption
|
||
} from 'naive-ui'
|
||
import {
|
||
Clapperboard,
|
||
Camera,
|
||
Images,
|
||
Palette,
|
||
Fingerprint,
|
||
FolderOpen,
|
||
FileText,
|
||
Layers,
|
||
PanelLeftClose,
|
||
PanelLeftOpen,
|
||
Settings2
|
||
} from '@lucide/vue'
|
||
import { AppDialog } from './components/ui'
|
||
import { useTheme } from './composables/useTheme'
|
||
|
||
/** 应用外壳固定在视口内,业务数据仍由各工作区自行加载。 */
|
||
const route = useRoute()
|
||
const collapsed = ref(window.matchMedia('(max-width: 800px)').matches)
|
||
const settingsOpen = ref(false)
|
||
const { preference, theme, overrides } = useTheme()
|
||
const projectId = computed(() => String(route.params.projectId || ''))
|
||
const apiBase = import.meta.env.VITE_API_BASE_URL || '/api'
|
||
const themeOptions = [
|
||
{ label: '浅色模式', value: 'light' },
|
||
{ label: '暗黑模式', value: 'dark' },
|
||
{ label: '跟随系统', value: 'system' }
|
||
]
|
||
const workflowItems = [
|
||
['create-drama', '剧本创作', FileText],
|
||
['breakdown', '剧本拆解', Layers],
|
||
['visual-style', '视觉风格', Palette],
|
||
['subject-identity', '主体身份', Fingerprint],
|
||
['subject-images', '形态图片', Images],
|
||
['storyboard', '分镜设计', Camera],
|
||
['production', '镜头生产', Clapperboard]
|
||
] as const
|
||
/** 菜单链接使用 RouterLink,保留新标签打开和浏览器导航行为。 */
|
||
const menuOptions = computed<MenuOption[]>(() => [
|
||
{
|
||
key: '/projects',
|
||
label: () => h(RouterLink, { to: '/projects' }, () => '我的剧本'),
|
||
icon: () => h(FolderOpen, { size: 18 })
|
||
},
|
||
...(projectId.value
|
||
? [
|
||
{ type: 'divider' as const, key: 'divider' },
|
||
...workflowItems.map(([path, label, icon]) => ({
|
||
key: '/projects/' + projectId.value + '/' + path,
|
||
label: () => h(RouterLink, { to: '/projects/' + projectId.value + '/' + path }, () => label),
|
||
icon: () => h(icon, { size: 18 })
|
||
}))
|
||
]
|
||
: [])
|
||
])
|
||
</script>
|
||
|
||
<template>
|
||
<NConfigProvider
|
||
:theme="theme"
|
||
:theme-overrides="overrides"
|
||
:locale="zhCN"
|
||
:date-locale="dateZhCN"
|
||
class="app-provider"
|
||
>
|
||
<NGlobalStyle />
|
||
<a href="#main-content" class="skip-link">跳到主要内容</a>
|
||
<NLayout has-sider class="admin-layout">
|
||
<NLayoutSider
|
||
bordered
|
||
collapse-mode="width"
|
||
:collapsed="collapsed"
|
||
:width="208"
|
||
:collapsed-width="64"
|
||
:native-scrollbar="false"
|
||
class="admin-sider"
|
||
>
|
||
<RouterLink to="/projects" class="admin-brand" aria-label="短剧工作台首页">
|
||
<Clapperboard :size="24" /><span v-if="!collapsed"
|
||
>短剧工作台<small>SHORT DRAMA STUDIO</small></span
|
||
>
|
||
</RouterLink>
|
||
<NMenu
|
||
:value="route.path"
|
||
:options="menuOptions"
|
||
:collapsed="collapsed"
|
||
:collapsed-width="64"
|
||
:collapsed-icon-size="19"
|
||
/>
|
||
</NLayoutSider>
|
||
<NLayout class="admin-main">
|
||
<NLayoutHeader bordered class="admin-topbar">
|
||
<div class="flex min-w-0 items-center gap-3">
|
||
<NButton
|
||
quaternary
|
||
circle
|
||
:aria-label="collapsed ? '展开侧栏' : '折叠侧栏'"
|
||
@click="collapsed = !collapsed"
|
||
>
|
||
<PanelLeftOpen v-if="collapsed" :size="18" /><PanelLeftClose v-else :size="18" />
|
||
</NButton>
|
||
<span class="truncate text-sm">{{ route.meta.title || '工作空间' }}</span>
|
||
</div>
|
||
<div class="flex items-center gap-3">
|
||
<NSelect
|
||
v-model:value="preference"
|
||
:options="themeOptions"
|
||
:virtual-scroll="false"
|
||
size="small"
|
||
class="theme-select"
|
||
aria-label="主题模式"
|
||
/>
|
||
<NTooltip
|
||
><template #trigger
|
||
><NButton quaternary circle aria-label="后端连接" @click="settingsOpen = true"
|
||
><Settings2 :size="18" /></NButton></template
|
||
>后端连接</NTooltip
|
||
>
|
||
</div>
|
||
</NLayoutHeader>
|
||
<main id="main-content" tabindex="-1" class="admin-content"><RouterView /></main>
|
||
</NLayout>
|
||
</NLayout>
|
||
<AppDialog
|
||
v-model:open="settingsOpen"
|
||
title="后端连接"
|
||
description="页面使用真实后端接口,生成操作会实际提交。"
|
||
>
|
||
<dl class="detail-grid mt-6">
|
||
<dt>API 地址</dt>
|
||
<dd>
|
||
<code>{{ apiBase }}</code>
|
||
</dd>
|
||
<dt>开发环境</dt>
|
||
<dd>在 .env 中配置 API_PROXY_TARGET,修改后重启 Vite。</dd>
|
||
<dt>生产环境</dt>
|
||
<dd>为 /api、/storage 配置反向代理,并为长工作流设置足够的超时。</dd>
|
||
<dt>状态更新</dt>
|
||
<dd>项目详情每 6 秒刷新;关闭页面不会取消后端任务。</dd>
|
||
</dl>
|
||
</AppDialog>
|
||
</NConfigProvider>
|
||
</template>
|