style: 更新品牌标识与侧栏切换图标
This commit is contained in:
+13
-13
@@ -14,8 +14,8 @@ import {
|
|||||||
type MenuOption
|
type MenuOption
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import {
|
import {
|
||||||
Archive,
|
|
||||||
Clapperboard,
|
Clapperboard,
|
||||||
|
Archive,
|
||||||
Camera,
|
Camera,
|
||||||
Images,
|
Images,
|
||||||
Palette,
|
Palette,
|
||||||
@@ -23,11 +23,12 @@ import {
|
|||||||
FolderOpen,
|
FolderOpen,
|
||||||
FileText,
|
FileText,
|
||||||
Layers,
|
Layers,
|
||||||
Menu,
|
PanelLeftClose,
|
||||||
|
PanelLeftOpen,
|
||||||
Settings2,
|
Settings2,
|
||||||
X
|
X
|
||||||
} from '@lucide/vue'
|
} from '@lucide/vue'
|
||||||
import { AppDialog } from './components/ui'
|
import { AppDialog, StudioLogo } from './components/ui'
|
||||||
import { useTheme } from './composables/useTheme'
|
import { useTheme } from './composables/useTheme'
|
||||||
import ThemeToggle from './components/ui/ThemeToggle.vue'
|
import ThemeToggle from './components/ui/ThemeToggle.vue'
|
||||||
import { projectAccessKey, type ProjectAccess } from './features/projects/access'
|
import { projectAccessKey, type ProjectAccess } from './features/projects/access'
|
||||||
@@ -195,7 +196,7 @@ const menuOptions = computed<MenuOption[]>(() => {
|
|||||||
:class="{ 'is-collapsed': collapsed }"
|
:class="{ 'is-collapsed': collapsed }"
|
||||||
aria-label="短剧工作台首页"
|
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
|
>短剧工作台<small>SHORT DRAMA STUDIO</small></span
|
||||||
>
|
>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
@@ -249,7 +250,9 @@ const menuOptions = computed<MenuOption[]>(() => {
|
|||||||
aria-controls="main-navigation"
|
aria-controls="main-navigation"
|
||||||
@click="toggleNav"
|
@click="toggleNav"
|
||||||
>
|
>
|
||||||
<template #icon><Menu :size="20" /></template>
|
<template #icon
|
||||||
|
><component :is="collapsed ? PanelLeftOpen : PanelLeftClose" :size="20"
|
||||||
|
/></template>
|
||||||
</NButton>
|
</NButton>
|
||||||
<span class="truncate text-sm">{{ route.meta.title || '工作空间' }}</span>
|
<span class="truncate text-sm">{{ route.meta.title || '工作空间' }}</span>
|
||||||
</div>
|
</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;
|
@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);
|
min-width: var(--admin-sider-expanded);
|
||||||
}
|
}
|
||||||
.admin-brand > svg {
|
.admin-brand-copy {
|
||||||
@apply shrink-0;
|
|
||||||
}
|
|
||||||
.admin-brand span {
|
|
||||||
@apply text-[15px] font-semibold;
|
@apply text-[15px] font-semibold;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition:
|
transition:
|
||||||
opacity 0.2s ease,
|
opacity 0.2s ease,
|
||||||
transform 0.2s ease;
|
transform 0.2s ease;
|
||||||
}
|
}
|
||||||
.admin-sider.is-collapsed .admin-brand span {
|
.admin-sider.is-collapsed .admin-brand-copy {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
.admin-brand small {
|
.admin-brand-copy small {
|
||||||
@apply block text-[8px] tracking-[1px] font-normal text-muted mt-[3px];
|
@apply block text-[8px] tracking-[1px] font-normal text-muted mt-[3px];
|
||||||
}
|
}
|
||||||
.admin-topbar {
|
.admin-topbar {
|
||||||
@@ -525,7 +525,7 @@ const menuOptions = computed<MenuOption[]>(() => {
|
|||||||
.admin-brand {
|
.admin-brand {
|
||||||
@apply h-12 min-w-0 flex-1 justify-start px-3.5;
|
@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 .n-menu-item-content-header,
|
||||||
.admin-sider.is-collapsed .admin-settings-label {
|
.admin-sider.is-collapsed .admin-settings-label {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -545,7 +545,7 @@ const menuOptions = computed<MenuOption[]>(() => {
|
|||||||
.admin-sider.is-collapsed .admin-sider-footer,
|
.admin-sider.is-collapsed .admin-sider-footer,
|
||||||
.admin-sider .n-menu-item-content-header,
|
.admin-sider .n-menu-item-content-header,
|
||||||
.admin-menu-label,
|
.admin-menu-label,
|
||||||
.admin-brand span,
|
.admin-brand-copy,
|
||||||
.admin-settings-label {
|
.admin-settings-label {
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
export { default as AppDialog } from './AppDialog.vue'
|
export { default as AppDialog } from './AppDialog.vue'
|
||||||
export { default as EmptyState } from './EmptyState.vue'
|
export { default as EmptyState } from './EmptyState.vue'
|
||||||
export { default as StatusBadge } from './StatusBadge.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 AssetImage } from './AssetImage.vue'
|
||||||
export { default as RevealImage } from './RevealImage.vue'
|
export { default as RevealImage } from './RevealImage.vue'
|
||||||
export { default as DirectoryItem } from './DirectoryItem.vue'
|
export { default as DirectoryItem } from './DirectoryItem.vue'
|
||||||
|
|||||||
@@ -132,7 +132,9 @@ describe('管理后台组件边界', () => {
|
|||||||
expect(wrapper.getComponent(ThemeToggle).findComponent(NTooltip).exists()).toBe(false)
|
expect(wrapper.getComponent(ThemeToggle).findComponent(NTooltip).exists()).toBe(false)
|
||||||
expect(wrapper.get('.theme-toggle').attributes('title')).toBeUndefined()
|
expect(wrapper.get('.theme-toggle').attributes('title')).toBeUndefined()
|
||||||
expect(wrapper.get('.theme-toggle svg').classes()).toContain('lucide-monitor')
|
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'
|
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||||||
for (const [key, label, icon] of [
|
for (const [key, label, icon] of [
|
||||||
['dark', '暗黑模式', 'moon'],
|
['dark', '暗黑模式', 'moon'],
|
||||||
@@ -173,9 +175,14 @@ describe('管理后台组件边界', () => {
|
|||||||
}
|
}
|
||||||
expect(firstMenuIcon().type).not.toBe(NPopover)
|
expect(firstMenuIcon().type).not.toBe(NPopover)
|
||||||
expect(wrapper.get('.admin-sider-footer').findComponent(NPopover).exists()).toBe(false)
|
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 wrapper.get('[aria-label="折叠侧栏"]').trigger('click')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(wrapper.find('[aria-label="展开侧栏"]').exists()).toBe(true)
|
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(wrapper.getComponent(NMenu).props('collapsed')).toBe(false)
|
||||||
expect(firstMenuIcon().type).not.toBe(NPopover)
|
expect(firstMenuIcon().type).not.toBe(NPopover)
|
||||||
expect(wrapper.get('.admin-sider').attributes('style')).toContain('width: 64px')
|
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\.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-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-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).toMatch(/@media \(max-width: 800px\)[\s\S]*?\.admin-brand\s*\{[^}]*height:\s*48px/)
|
||||||
expect(css).toContain('@media (max-width: 800px)')
|
expect(css).toContain('@media (max-width: 800px)')
|
||||||
expect(css).toMatch(/\.admin-sider\s*\{[^}]*transition:\s*width\s+0\.32s/)
|
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="后端连接"]')
|
const collapsedSettings = wrapper.get('.admin-sider-footer [aria-label="后端连接"]')
|
||||||
expect(collapsedSettings.text()).toBe('后端连接')
|
expect(collapsedSettings.text()).toBe('后端连接')
|
||||||
expect(collapsedSettings.classes()).toContain('is-collapsed')
|
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 wrapper.get('.admin-topbar [aria-label="打开主菜单"]').trigger('click')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(wrapper.findAll('.admin-sider')).toHaveLength(1)
|
expect(wrapper.findAll('.admin-sider')).toHaveLength(1)
|
||||||
@@ -307,6 +314,9 @@ describe('管理后台组件边界', () => {
|
|||||||
expect(wrapper.find('.admin-sider-mask').exists()).toBe(false)
|
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-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="关闭主菜单"]').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')
|
const expandedBrand = wrapper.get('.admin-brand')
|
||||||
expect(expandedBrand.classes()).not.toContain('is-collapsed')
|
expect(expandedBrand.classes()).not.toContain('is-collapsed')
|
||||||
expect(expandedBrand.text()).toContain('短剧工作台')
|
expect(expandedBrand.text()).toContain('短剧工作台')
|
||||||
@@ -384,7 +394,7 @@ describe('管理后台组件边界', () => {
|
|||||||
expect(document.querySelector('.n-drawer-container')).toBeNull()
|
expect(document.querySelector('.n-drawer-container')).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('完成项目解锁全部左侧链接,移除项目标题栏并将项目名写入浏览器标题', async () => {
|
it('完成项目解锁全部左侧链接,标题不再显示状态标签和刷新按钮', async () => {
|
||||||
const detail = vi.spyOn(projectsApi, 'detail').mockResolvedValue({
|
const detail = vi.spyOn(projectsApi, 'detail').mockResolvedValue({
|
||||||
id: 'navigation-test',
|
id: 'navigation-test',
|
||||||
title: '导航测试项目',
|
title: '导航测试项目',
|
||||||
@@ -424,8 +434,7 @@ describe('管理后台组件边界', () => {
|
|||||||
wrapper = mount(App, { attachTo: document.body, global: { plugins: [router] } })
|
wrapper = mount(App, { attachTo: document.body, global: { plugins: [router] } })
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(wrapper.find('[aria-label="项目工作流"]').exists()).toBe(false)
|
expect(wrapper.find('[aria-label="项目工作流"]').exists()).toBe(false)
|
||||||
expect(wrapper.find('.project-header').exists()).toBe(false)
|
expect(wrapper.get('.project-title').text()).toBe('导航测试项目')
|
||||||
expect(document.title).toBe('导航测试项目 · 工作空间 · 短剧工作台')
|
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
await wrapper.get(`.n-menu a[href="/projects/navigation-test/${path}"]`).trigger('click')
|
await wrapper.get(`.n-menu a[href="/projects/navigation-test/${path}"]`).trigger('click')
|
||||||
await flushPromises()
|
await flushPromises()
|
||||||
@@ -433,6 +442,8 @@ describe('管理后台组件边界', () => {
|
|||||||
expect(wrapper.get('.project-view .workspace-page').text()).toBe(path)
|
expect(wrapper.get('.project-view .workspace-page').text()).toBe(path)
|
||||||
}
|
}
|
||||||
expect(detail).toHaveBeenCalledOnce()
|
expect(detail).toHaveBeenCalledOnce()
|
||||||
|
expect(wrapper.find('.project-header .n-button').exists()).toBe(false)
|
||||||
|
expect(wrapper.find('.project-header .n-tag').exists()).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('确认按钮有稳定根元素承接调用方间距,生产按钮显式保留上下留白', () => {
|
it('确认按钮有稳定根元素承接调用方间距,生产按钮显式保留上下留白', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user