From eec957a2e47ff3a37d3de574f898b9872a9ec5bc Mon Sep 17 00:00:00 2001 From: GouJ Date: Tue, 22 Sep 2026 09:28:38 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=B0=86=E8=B5=84=E4=BA=A7?= =?UTF-8?q?=E5=BA=93=E7=A7=BB=E8=87=B3=E5=85=A8=E5=B1=80=E5=AF=BC=E8=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.vue | 6 +- .../project-assets/AssetLibraryPage.vue | 121 ++++++++++++++++++ src/router/index.ts | 5 + tests/components/ui/layout.test.ts | 2 +- tests/features/projects/access.test.ts | 8 +- 5 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 src/features/project-assets/AssetLibraryPage.vue diff --git a/src/App.vue b/src/App.vue index 36e9215..0567808 100644 --- a/src/App.vue +++ b/src/App.vue @@ -108,7 +108,6 @@ const workflowItems = [ ['visual-style', '视觉风格', Palette], ['subject-identity', '主体身份', Fingerprint], ['subject-images', '形态图片', Images], - ['assets', '资产库', Archive], ['storyboard', '分镜设计', Camera], ['production', '镜头生产', Clapperboard] ] as const @@ -126,6 +125,11 @@ const menuOptions = computed(() => { label: () => h(RouterLink, { to: '/projects' }, () => h('span', { class: 'admin-menu-label' }, '我的剧本')), icon: createMenuIcon(FolderOpen) }, + { + key: '/assets', + label: () => h(RouterLink, { to: '/assets' }, () => h('span', { class: 'admin-menu-label' }, '资产库')), + icon: createMenuIcon(Archive) + }, ...(projectId.value ? [ { type: 'divider' as const, key: 'divider' }, diff --git a/src/features/project-assets/AssetLibraryPage.vue b/src/features/project-assets/AssetLibraryPage.vue new file mode 100644 index 0000000..d55d31b --- /dev/null +++ b/src/features/project-assets/AssetLibraryPage.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/src/router/index.ts b/src/router/index.ts index f24b112..ae9682a 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -11,6 +11,11 @@ export const router = createRouter({ component: () => import('../features/projects/ProjectsPage.vue'), meta: { title: '我的剧本' } }, + { + path: '/assets', + component: () => import('../features/project-assets/AssetLibraryPage.vue'), + meta: { title: '资产库' } + }, { path: '/projects/:projectId', component: () => import('../features/projects/ProjectLayout.vue'), diff --git a/tests/components/ui/layout.test.ts b/tests/components/ui/layout.test.ts index 660f88d..f0627c0 100644 --- a/tests/components/ui/layout.test.ts +++ b/tests/components/ui/layout.test.ts @@ -189,12 +189,12 @@ describe('管理后台组件边界', () => { const labels = wrapper.findAll('.n-menu-item-content-header').map(item => item.text()) expect(labels).toEqual([ '我的剧本', + '资产库', '剧本创作', '剧本拆解', '视觉风格', '主体身份', '形态图片', - '资产库', '分镜设计', '镜头生产' ]) diff --git a/tests/features/projects/access.test.ts b/tests/features/projects/access.test.ts index 520cd9d..6303e8c 100644 --- a/tests/features/projects/access.test.ts +++ b/tests/features/projects/access.test.ts @@ -107,7 +107,7 @@ describe('剧本完成前的下游访问限制', () => { expect(wrapper!.find(`.n-menu a[href="/projects/unfinished/${path}"]`).exists()).toBe(false) } expect(mounted).not.toHaveBeenCalled() - expect(wrapper!.findAll('.n-menu [aria-disabled="true"]')).toHaveLength(7) + expect(wrapper!.findAll('.n-menu [aria-disabled="true"]')).toHaveLength(6) await wrapper!.get('.project-access-gate button').trigger('click') await flushPromises() expect(router.currentRoute.value.path).toBe('/projects/unfinished/create-drama') @@ -147,7 +147,7 @@ describe('剧本完成前的下游访问限制', () => { expect(wrapper!.findAll('.n-menu a')).toHaveLength(9) await router.push('/projects/second/production') await flushPromises() - expect(wrapper!.findAll('.n-menu a')).toHaveLength(2) + expect(wrapper!.findAll('.n-menu a')).toHaveLength(3) expect(wrapper!.find('.workspace-probe').exists()).toBe(false) resolveSecond(project('second', 'draft')) await flushPromises() @@ -155,7 +155,7 @@ describe('剧本完成前的下游访问限制', () => { expect(mounted).toHaveBeenCalledExactlyOnceWith('production') await router.push('/projects') await flushPromises() - expect(wrapper!.findAll('.n-menu a')).toHaveLength(1) + expect(wrapper!.findAll('.n-menu a')).toHaveLength(2) }) it('读取失败保持锁定;错误区重试成功后解锁且不恢复项目标题栏', async () => { @@ -166,7 +166,7 @@ describe('剧本完成前的下游访问限制', () => { vi.spyOn(projectsApi, 'checkpoints').mockResolvedValue([]) const { mounted } = await openProject('/projects/retry/storyboard') expect(mounted).not.toHaveBeenCalled() - expect(wrapper!.findAll('.n-menu a')).toHaveLength(2) + expect(wrapper!.findAll('.n-menu a')).toHaveLength(3) expect(wrapper!.find('.project-header').exists()).toBe(false) await wrapper!.get('.project-notices button').trigger('click') await flushPromises()