style: 更新品牌标识与侧栏切换图标

This commit is contained in:
GouJ
2026-09-22 19:16:40 +08:00
parent 7763170830
commit a442087ea9
4 changed files with 63 additions and 19 deletions
+13 -13
View File
@@ -14,8 +14,8 @@ import {
type MenuOption
} from 'naive-ui'
import {
Archive,
Clapperboard,
Archive,
Camera,
Images,
Palette,
@@ -23,11 +23,12 @@ import {
FolderOpen,
FileText,
Layers,
Menu,
PanelLeftClose,
PanelLeftOpen,
Settings2,
X
} from '@lucide/vue'
import { AppDialog } from './components/ui'
import { AppDialog, StudioLogo } from './components/ui'
import { useTheme } from './composables/useTheme'
import ThemeToggle from './components/ui/ThemeToggle.vue'
import { projectAccessKey, type ProjectAccess } from './features/projects/access'
@@ -195,7 +196,7 @@ const menuOptions = computed<MenuOption[]>(() => {
:class="{ 'is-collapsed': collapsed }"
aria-label="短剧工作台首页"
>
<Clapperboard :size="narrow ? NAV_ICON_SIZE : 24" /><span
<StudioLogo :size="narrow ? 23 : 27" /><span class="admin-brand-copy"
>短剧工作台<small>SHORT DRAMA STUDIO</small></span
>
</RouterLink>
@@ -249,7 +250,9 @@ const menuOptions = computed<MenuOption[]>(() => {
aria-controls="main-navigation"
@click="toggleNav"
>
<template #icon><Menu :size="20" /></template>
<template #icon
><component :is="collapsed ? PanelLeftOpen : PanelLeftClose" :size="20"
/></template>
</NButton>
<span class="truncate text-sm">{{ route.meta.title || '工作空间' }}</span>
</div>
@@ -362,20 +365,17 @@ const menuOptions = computed<MenuOption[]>(() => {
@apply flex shrink-0 items-center gap-3 h-[74px] overflow-hidden py-0 px-5 text-ink whitespace-nowrap;
min-width: var(--admin-sider-expanded);
}
.admin-brand > svg {
@apply shrink-0;
}
.admin-brand span {
.admin-brand-copy {
@apply text-[15px] font-semibold;
opacity: 1;
transition:
opacity 0.2s ease,
transform 0.2s ease;
}
.admin-sider.is-collapsed .admin-brand span {
.admin-sider.is-collapsed .admin-brand-copy {
opacity: 0;
}
.admin-brand small {
.admin-brand-copy small {
@apply block text-[8px] tracking-[1px] font-normal text-muted mt-[3px];
}
.admin-topbar {
@@ -525,7 +525,7 @@ const menuOptions = computed<MenuOption[]>(() => {
.admin-brand {
@apply h-12 min-w-0 flex-1 justify-start px-3.5;
}
.admin-sider.is-collapsed .admin-brand span,
.admin-sider.is-collapsed .admin-brand-copy,
.admin-sider.is-collapsed .n-menu-item-content-header,
.admin-sider.is-collapsed .admin-settings-label {
opacity: 1;
@@ -545,7 +545,7 @@ const menuOptions = computed<MenuOption[]>(() => {
.admin-sider.is-collapsed .admin-sider-footer,
.admin-sider .n-menu-item-content-header,
.admin-menu-label,
.admin-brand span,
.admin-brand-copy,
.admin-settings-label {
transition: none;
}
+32
View File
@@ -0,0 +1,32 @@
<script setup lang="ts">
import { Film, Sparkles } from '@lucide/vue'
withDefaults(defineProps<{ size?: number }>(), { size: 28 })
</script>
<template>
<span class="studio-logo" :style="{ width: `${size}px`, height: `${size}px` }" aria-hidden="true">
<Film class="studio-logo-film" :size="size" :stroke-width="1.8" />
<Sparkles class="studio-logo-ai" :size="Math.round(size * 0.46)" :stroke-width="2.4" />
</span>
</template>
<style scoped>
.studio-logo {
position: relative;
display: inline-grid;
flex: 0 0 auto;
place-items: center;
color: var(--app-ink);
}
.studio-logo-film {
display: block;
}
.studio-logo-ai {
position: absolute;
top: -12%;
right: -15%;
color: var(--app-accent-text);
fill: var(--app-subtle);
}
</style>
+1
View File
@@ -2,6 +2,7 @@
export { default as AppDialog } from './AppDialog.vue'
export { default as EmptyState } from './EmptyState.vue'
export { default as StatusBadge } from './StatusBadge.vue'
export { default as StudioLogo } from './StudioLogo.vue'
export { default as AssetImage } from './AssetImage.vue'
export { default as RevealImage } from './RevealImage.vue'
export { default as DirectoryItem } from './DirectoryItem.vue'
+17 -6
View File
@@ -132,7 +132,9 @@ describe('管理后台组件边界', () => {
expect(wrapper.getComponent(ThemeToggle).findComponent(NTooltip).exists()).toBe(false)
expect(wrapper.get('.theme-toggle').attributes('title')).toBeUndefined()
expect(wrapper.get('.theme-toggle svg').classes()).toContain('lucide-monitor')
expect(wrapper.get('.admin-topbar [aria-controls="main-navigation"] svg').classes()).toContain('lucide-menu')
expect(wrapper.get('.admin-topbar [aria-controls="main-navigation"] svg').classes()).toContain(
'lucide-panel-left-close'
)
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
for (const [key, label, icon] of [
['dark', '暗黑模式', 'moon'],
@@ -173,9 +175,14 @@ describe('管理后台组件边界', () => {
}
expect(firstMenuIcon().type).not.toBe(NPopover)
expect(wrapper.get('.admin-sider-footer').findComponent(NPopover).exists()).toBe(false)
expect(wrapper.get('[aria-label="折叠侧栏"] svg').classes()).toContain('lucide-panel-left-close')
expect(wrapper.findAll('.studio-logo svg')).toHaveLength(2)
expect(wrapper.find('.studio-logo .lucide-film').exists()).toBe(true)
expect(wrapper.find('.studio-logo .lucide-sparkles').exists()).toBe(true)
await wrapper.get('[aria-label="折叠侧栏"]').trigger('click')
await flushPromises()
expect(wrapper.find('[aria-label="展开侧栏"]').exists()).toBe(true)
expect(wrapper.get('[aria-label="展开侧栏"] svg').classes()).toContain('lucide-panel-left-open')
expect(wrapper.getComponent(NMenu).props('collapsed')).toBe(false)
expect(firstMenuIcon().type).not.toBe(NPopover)
expect(wrapper.get('.admin-sider').attributes('style')).toContain('width: 64px')
@@ -216,7 +223,7 @@ describe('管理后台组件边界', () => {
expect(css).toMatch(/\.admin-sider\.is-collapsed \.n-menu-item-content__icon\s*\{[^}]*pointer-events:\s*none/)
expect(css).toMatch(/\.admin-sider-footer\s*\{[^}]*flex-shrink:\s*0/)
expect(css).toMatch(/\.admin-sider\.is-collapsed \.admin-settings-label\s*\{[^}]*opacity:\s*0/)
expect(css).toMatch(/\.admin-sider\.is-collapsed \.admin-brand span\s*\{[^}]*opacity:\s*0/)
expect(css).toMatch(/\.admin-sider\.is-collapsed \.admin-brand-copy\s*\{[^}]*opacity:\s*0/)
expect(css).toMatch(/@media \(max-width: 800px\)[\s\S]*?\.admin-brand\s*\{[^}]*height:\s*48px/)
expect(css).toContain('@media (max-width: 800px)')
expect(css).toMatch(/\.admin-sider\s*\{[^}]*transition:\s*width\s+0\.32s/)
@@ -296,7 +303,7 @@ describe('管理后台组件边界', () => {
const collapsedSettings = wrapper.get('.admin-sider-footer [aria-label="后端连接"]')
expect(collapsedSettings.text()).toBe('后端连接')
expect(collapsedSettings.classes()).toContain('is-collapsed')
expect(wrapper.get('.admin-topbar [aria-label="打开主菜单"] svg').classes()).toContain('lucide-menu')
expect(wrapper.get('.admin-topbar [aria-label="打开主菜单"] svg').classes()).toContain('lucide-panel-left-open')
await wrapper.get('.admin-topbar [aria-label="打开主菜单"]').trigger('click')
await flushPromises()
expect(wrapper.findAll('.admin-sider')).toHaveLength(1)
@@ -307,6 +314,9 @@ describe('管理后台组件边界', () => {
expect(wrapper.find('.admin-sider-mask').exists()).toBe(false)
expect(wrapper.get('.admin-sider').findAll('.n-menu .n-menu-item')).toHaveLength(9)
expect(wrapper.get('.admin-topbar [aria-label="关闭主菜单"]').attributes('aria-expanded')).toBe('true')
expect(wrapper.get('.admin-topbar [aria-label="关闭主菜单"] svg').classes()).toContain(
'lucide-panel-left-close'
)
const expandedBrand = wrapper.get('.admin-brand')
expect(expandedBrand.classes()).not.toContain('is-collapsed')
expect(expandedBrand.text()).toContain('短剧工作台')
@@ -384,7 +394,7 @@ describe('管理后台组件边界', () => {
expect(document.querySelector('.n-drawer-container')).toBeNull()
})
it('完成项目解锁全部左侧链接,移除项目标题栏并将项目名写入浏览器标题', async () => {
it('完成项目解锁全部左侧链接,标题不再显示状态标签和刷新按钮', async () => {
const detail = vi.spyOn(projectsApi, 'detail').mockResolvedValue({
id: 'navigation-test',
title: '导航测试项目',
@@ -424,8 +434,7 @@ describe('管理后台组件边界', () => {
wrapper = mount(App, { attachTo: document.body, global: { plugins: [router] } })
await flushPromises()
expect(wrapper.find('[aria-label="项目工作流"]').exists()).toBe(false)
expect(wrapper.find('.project-header').exists()).toBe(false)
expect(document.title).toBe('导航测试项目 · 工作空间 · 短剧工作台')
expect(wrapper.get('.project-title').text()).toBe('导航测试项目')
for (const path of paths) {
await wrapper.get(`.n-menu a[href="/projects/navigation-test/${path}"]`).trigger('click')
await flushPromises()
@@ -433,6 +442,8 @@ describe('管理后台组件边界', () => {
expect(wrapper.get('.project-view .workspace-page').text()).toBe(path)
}
expect(detail).toHaveBeenCalledOnce()
expect(wrapper.find('.project-header .n-button').exists()).toBe(false)
expect(wrapper.find('.project-header .n-tag').exists()).toBe(false)
})
it('确认按钮有稳定根元素承接调用方间距,生产按钮显式保留上下留白', () => {