From 2d0d7e22d35b83aa758bfa939a1f8bea1623b62a Mon Sep 17 00:00:00 2001 From: GouJ Date: Tue, 22 Sep 2026 11:05:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9C=A8=E5=85=A8=E5=B1=80=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E5=BA=93=E5=86=85=E5=AE=8C=E6=88=90=E7=B4=A0=E6=9D=90?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project-assets/AssetLibraryPage.vue | 270 +++++++++++++++--- tests/features/project-assets/library.test.ts | 90 +++--- 2 files changed, 286 insertions(+), 74 deletions(-) diff --git a/src/features/project-assets/AssetLibraryPage.vue b/src/features/project-assets/AssetLibraryPage.vue index a3c9fb1..3de3efd 100644 --- a/src/features/project-assets/AssetLibraryPage.vue +++ b/src/features/project-assets/AssetLibraryPage.vue @@ -1,14 +1,21 @@ + {{ fileError }} {{ query.error.value }}重新连接 + + 剧本列表读取失败,暂时不能上传新图片。 + 重新读取 +
(() => filteredAssets.value) :options="categoryOptions" aria-label="筛选资产分类" /> - {{ typedAssets.length }} / {{ assets.length }} 张 + {{ filteredAssets.length }} / {{ assets.length }} 张
-
-
+
+
@@ -140,15 +285,9 @@ const typedAssets = computed(() => filteredAssets.value) {{ asset.extension.toUpperCase() }} · {{ formatBytes(asset.size) }} - - - 管理 - - + + 管理 +
@@ -157,14 +296,72 @@ const typedAssets = computed(() => filteredAssets.value) 清除筛选 - - 前往我的剧本 - + + 上传第一张图片 +

正在读取全部资产图片…

+ + + {{ actionError }} + +

{{ selectedFile?.name }} · {{ formatBytes(selectedFile?.size ?? 0) }}

+ + + + + + + +
+ 取消 + 确认上传 +
+
+
+ + + {{ actionError }} + + + + + + +
+ + + 删除后物理文件也会移除;正在被视觉风格引用的素材会由后端阻止删除。 + +
+ 取消 + 保存修改 +
+
+
+
@@ -197,6 +394,9 @@ const typedAssets = computed(() => filteredAssets.value) } } @container asset-library (max-width: 560px) { + .asset-library-heading { + @apply items-start; + } .asset-library-filters { @apply grid-cols-1; } diff --git a/tests/features/project-assets/library.test.ts b/tests/features/project-assets/library.test.ts index 6e489a4..cf34569 100644 --- a/tests/features/project-assets/library.test.ts +++ b/tests/features/project-assets/library.test.ts @@ -13,44 +13,48 @@ afterEach(() => { describe('全局资产库', () => { it('单次读取并直接显示全部剧本图片,支持按图片与剧本搜索', async () => { - const fetcher = vi.fn().mockResolvedValue( - new Response( - JSON.stringify({ - data: [ - { - id: 'asset-scene', - projectId: 'project-one', - name: '雨夜街道', - type: 'image', - category: 'scene', - mimeType: 'image/png', - extension: 'png', - size: 4096, - publicUrl: '/storage/scene.png', - metadata: null, - createdAt: '2026-09-22T00:00:00Z', - updatedAt: '2026-09-22T00:00:00Z', - project: { id: 'project-one', title: '记忆当铺', topic: '记忆交易', status: 'completed' } - }, - { - id: 'asset-character', - projectId: 'project-two', - name: '林默正面照', - type: 'image', - category: 'character', - mimeType: 'image/jpeg', - extension: 'jpg', - size: 8192, - publicUrl: '/storage/character.jpg', - metadata: null, - createdAt: '2026-09-22T01:00:00Z', - updatedAt: '2026-09-22T01:00:00Z', - project: { id: 'project-two', title: '天降甘霖', topic: '乡村故事', status: 'completed' } - } - ] - }) - ) - ) + const assets = [ + { + id: 'asset-scene', + projectId: 'project-one', + name: '雨夜街道', + type: 'image', + category: 'scene', + mimeType: 'image/png', + extension: 'png', + size: 4096, + publicUrl: '/storage/scene.png', + metadata: null, + createdAt: '2026-09-22T00:00:00Z', + updatedAt: '2026-09-22T00:00:00Z', + project: { id: 'project-one', title: '记忆当铺', topic: '记忆交易', status: 'completed' } + }, + { + id: 'asset-character', + projectId: 'project-two', + name: '林默正面照', + type: 'image', + category: 'character', + mimeType: 'image/jpeg', + extension: 'jpg', + size: 8192, + publicUrl: '/storage/character.jpg', + metadata: null, + createdAt: '2026-09-22T01:00:00Z', + updatedAt: '2026-09-22T01:00:00Z', + project: { id: 'project-two', title: '天降甘霖', topic: '乡村故事', status: 'completed' } + } + ] + const projects = assets.map(asset => ({ + ...asset.project, + style: null, + createdAt: '', + updatedAt: '' + })) + const fetcher = vi.fn().mockImplementation(async input => { + const url = String(input) + return new Response(JSON.stringify({ data: url.endsWith('/projects') ? projects : assets })) + }) vi.stubGlobal('fetch', fetcher) const router = createRouter({ history: createMemoryHistory(), @@ -63,13 +67,21 @@ describe('全局资产库', () => { wrapper = mount(AssetLibraryPage, { attachTo: document.body, global: { plugins: [router] } }) await flushPromises() - expect(fetcher).toHaveBeenCalledOnce() + expect(fetcher).toHaveBeenCalledTimes(2) expect(fetcher).toHaveBeenCalledWith('/api/assets', expect.objectContaining({ method: 'GET' })) expect(wrapper.findAll('.asset-library-card')).toHaveLength(2) expect(wrapper.text()).toContain('雨夜街道') expect(wrapper.text()).toContain('林默正面照') expect(wrapper.text()).toContain('记忆当铺') + await wrapper + .findAll('button') + .find(button => button.text() === '管理')! + .trigger('click') + await flushPromises() + expect(document.querySelector('[role="dialog"][aria-label="管理素材信息"]')).not.toBeNull() + expect(router.currentRoute.value.path).toBe('/assets') + await wrapper.get('input[aria-label="搜索资产图片"]').setValue('天降甘霖') expect(wrapper.findAll('.asset-library-card')).toHaveLength(1) expect(wrapper.text()).toContain('林默正面照')