From d9bd27935fdcf4db6f3ef96bfb2c6fee564afc9b Mon Sep 17 00:00:00 2001 From: huyan Date: Thu, 30 Jul 2026 10:45:11 +0800 Subject: [PATCH 1/5] feat(layout): add floating product navigation The global shell needs a persistent navigation surface without moving route awareness into shared UI. Add the LiveDemo-inspired split floating header, route-group highlighting, and Windup mark. Keep the header private to app layout and reserve page content space beneath it. --- frontend/public/windup-mark.svg | 16 +++++ frontend/src/app/layout/app-header.tsx | 98 ++++++++++++++++++++++++++ frontend/src/app/layout/index.tsx | 17 ++--- 3 files changed, 118 insertions(+), 13 deletions(-) create mode 100644 frontend/public/windup-mark.svg create mode 100644 frontend/src/app/layout/app-header.tsx diff --git a/frontend/public/windup-mark.svg b/frontend/public/windup-mark.svg new file mode 100644 index 0000000..5b16f25 --- /dev/null +++ b/frontend/public/windup-mark.svg @@ -0,0 +1,16 @@ + + Windup + 侧视机械小鸟标志 + + + + + + + + + + + + + diff --git a/frontend/src/app/layout/app-header.tsx b/frontend/src/app/layout/app-header.tsx new file mode 100644 index 0000000..9ec3461 --- /dev/null +++ b/frontend/src/app/layout/app-header.tsx @@ -0,0 +1,98 @@ +import { Link, useLocation } from 'react-router' + +interface ProductNavigationItem { + to: string + label: string + compactLabel?: string + isActive: (pathname: string) => boolean +} + +const productNavigation: ProductNavigationItem[] = [ + { + to: '/', + label: '首页', + isActive: (pathname) => pathname === '/', + }, + { + to: '/projects', + label: '项目资产', + compactLabel: '项目', + isActive: (pathname) => pathname.startsWith('/projects') || pathname.startsWith('/playtest'), + }, + { + to: '/quick-start', + label: '创作', + isActive: (pathname) => + pathname.startsWith('/quick-start') || pathname.startsWith('/workflow-editor'), + }, +] + +function getWorkspaceLabel(pathname: string): { title: string; detail: string } { + if (pathname.startsWith('/projects') || pathname.startsWith('/playtest')) { + return { title: '项目资产', detail: '角色、造型与动作' } + } + + if (pathname.startsWith('/quick-start') || pathname.startsWith('/workflow-editor')) { + return { title: '创作工作流', detail: '设定、生成与审核' } + } + + return { title: '角色资产工作台', detail: 'Windup' } +} + +/** 跨页面悬浮 Bar 知道产品路由,因此属于 app 外壳,不下沉到 shared/ui。 */ +export function AppHeader() { + const { pathname } = useLocation() + const workspace = getWorkspaceLabel(pathname) + + return ( +
+
+ + + Windup + + + + {workspace.title} + {workspace.detail} + +
+ + +
+ ) +} diff --git a/frontend/src/app/layout/index.tsx b/frontend/src/app/layout/index.tsx index aa21b82..aa84554 100644 --- a/frontend/src/app/layout/index.tsx +++ b/frontend/src/app/layout/index.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from 'react' -import { Link } from 'react-router' + +import { AppHeader } from './app-header' /** 跨页面常驻导航属于应用外壳,由 app 层统一承载。 */ @@ -12,18 +13,8 @@ export interface AppShellProps { export function AppShell({ children }: AppShellProps) { return (
- -
{children}
+ +
{children}
) } From 5350293e11af8ccd3b41b0c6554b0dd714729eb8 Mon Sep 17 00:00:00 2001 From: huyan Date: Thu, 30 Jul 2026 10:45:34 +0800 Subject: [PATCH 2/5] test(layout): cover floating navigation routes Route grouping controls which product navigation item is exposed as current. Render the header on a workflow route and assert the home, project, and creation links. Protect the app-level navigation contract without coupling page tests to layout markup. --- frontend/src/app/layout/app-header.test.tsx | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 frontend/src/app/layout/app-header.test.tsx diff --git a/frontend/src/app/layout/app-header.test.tsx b/frontend/src/app/layout/app-header.test.tsx new file mode 100644 index 0000000..72467eb --- /dev/null +++ b/frontend/src/app/layout/app-header.test.tsx @@ -0,0 +1,22 @@ +// @vitest-environment jsdom +import { cleanup, render, screen } from '@testing-library/react' +import { afterEach, describe, expect, it } from 'vitest' +import { MemoryRouter } from 'react-router' + +import { AppHeader } from './app-header' + +afterEach(cleanup) + +describe('AppHeader', () => { + it('保留三个产品入口,并将工作流路由归入创作', () => { + render( + + + , + ) + + expect(screen.getByRole('link', { name: '返回 Windup 首页' }).getAttribute('href')).toBe('/') + expect(screen.getByRole('link', { name: '项目资产' }).getAttribute('href')).toBe('/projects') + expect(screen.getByRole('link', { name: '创作' }).getAttribute('aria-current')).toBe('page') + }) +}) From 57851c4354c1ba49a9668df19dafea8567aff25f Mon Sep 17 00:00:00 2001 From: huyan Date: Thu, 30 Jul 2026 10:46:00 +0800 Subject: [PATCH 3/5] feat(home): add first-screen entry surface The root route needs a clear first screen for the two existing production entry points. Build a responsive editorial hero and page-private choice cards for quick start and projects. Preserve the existing routes while keeping workflow state outside the home page. --- frontend/src/pages/home/choice-card.tsx | 86 +++++++++++++++++++++ frontend/src/pages/home/index.tsx | 99 ++++++++++++++++++++++--- 2 files changed, 176 insertions(+), 9 deletions(-) create mode 100644 frontend/src/pages/home/choice-card.tsx diff --git a/frontend/src/pages/home/choice-card.tsx b/frontend/src/pages/home/choice-card.tsx new file mode 100644 index 0000000..55e88fe --- /dev/null +++ b/frontend/src/pages/home/choice-card.tsx @@ -0,0 +1,86 @@ +import { Link } from 'react-router' + +export type HomeChoiceCardTone = 'light' | 'dark' + +export interface HomeChoiceCardProps { + to: string + eyebrow: string + index: string + title: string + description: string + actionLabel: string + tone?: HomeChoiceCardTone +} + +/** Home 专用入口卡;只接收显示内容与目标路由,不持有业务状态。 */ +export function HomeChoiceCard({ + to, + eyebrow, + index, + title, + description, + actionLabel, + tone = 'light', +}: HomeChoiceCardProps) { + const dark = tone === 'dark' + + return ( + +