From 1a7874476655be5ae9e1e6f290e9ec0121d72d26 Mon Sep 17 00:00:00 2001 From: Gmaker689 <1711322114@qq.com> Date: Mon, 25 May 2026 21:07:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A6=96=E9=A1=B5=E7=B4=A0=E6=9D=90?= =?UTF-8?q?=E7=94=BB=E5=BB=8A=E5=A2=9E=E5=8A=A0=E5=88=86=E7=B1=BB=E7=AD=9B?= =?UTF-8?q?=E9=80=89=E6=A0=87=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端 SQL 增加 json_extract 提取素材 metaType (frame/spritesheet/preview) - 前端 AssetGallery 新增分类标签栏:全部/精灵表/帧/场景瓦片/背景/UI - 按 metaType + assetType 组合过滤展示 --- backend/internal/handler/generate.go | 6 +- frontend/src/api/types.ts | 1 + frontend/src/components/AssetGallery.tsx | 192 +++++++++++++++-------- 3 files changed, 131 insertions(+), 68 deletions(-) diff --git a/backend/internal/handler/generate.go b/backend/internal/handler/generate.go index a2671fe..0aa9bce 100755 --- a/backend/internal/handler/generate.go +++ b/backend/internal/handler/generate.go @@ -313,13 +313,14 @@ type RecentAssetItem struct { Format string `json:"format"` Prompt string `json:"prompt"` AssetType string `json:"assetType"` + MetaType string `json:"metaType"` // frame / spritesheet / preview TaskID string `json:"taskId"` CreatedAt string `json:"createdAt"` } // GetRecentAssets 获取最近生成的素材列表(已完成任务的全部素材)。 func GetRecentAssets(c *gin.Context) { - limit := 20 + limit := 60 var dbRows []struct { Key string @@ -327,6 +328,7 @@ func GetRecentAssets(c *gin.Context) { Format string Prompt string AssetType string + MetaType string TaskID string ProjectID uint CreatedAt string @@ -334,6 +336,7 @@ func GetRecentAssets(c *gin.Context) { db.GetDB().WithContext(c.Request.Context()). Raw(`SELECT a.key, a.url, a.format, t.prompt, t.asset_type as asset_type, + COALESCE(json_extract(a.metadata, '$.type'), 'frame') as meta_type, t.external_id as task_id, t.project_id, t.created_at as created_at FROM assets a INNER JOIN tasks t ON a.task_id = t.id @@ -350,6 +353,7 @@ func GetRecentAssets(c *gin.Context) { Format: r.Format, Prompt: r.Prompt, AssetType: r.AssetType, + MetaType: r.MetaType, TaskID: r.TaskID, CreatedAt: r.CreatedAt, } diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts index a4d22a5..d4ee998 100755 --- a/frontend/src/api/types.ts +++ b/frontend/src/api/types.ts @@ -105,6 +105,7 @@ export interface RecentAssetItem { format: string prompt: string assetType: string + metaType: string taskId: string createdAt: string } diff --git a/frontend/src/components/AssetGallery.tsx b/frontend/src/components/AssetGallery.tsx index 87b45e9..621a489 100644 --- a/frontend/src/components/AssetGallery.tsx +++ b/frontend/src/components/AssetGallery.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useEffect, useState, useMemo } from 'react' import { getRecentAssets } from '../api/generate' import type { RecentAssetItem } from '../api/types' @@ -9,9 +9,38 @@ const ASSET_TYPE_LABELS: Record = { animation: '动画', } +const CATEGORIES = [ + { key: 'all', label: '全部' }, + { key: 'spritesheet', label: '精灵表' }, + { key: 'frame', label: '帧' }, + { key: 'tileset', label: '场景瓦片' }, + { key: 'background', label: '背景' }, + { key: 'ui', label: 'UI' }, +] + +function matchCategory(item: RecentAssetItem, cat: string): boolean { + switch (cat) { + case 'all': + return true + case 'spritesheet': + return item.metaType === 'spritesheet' + case 'frame': + return item.metaType === 'frame' || item.metaType === 'preview' || item.format === 'gif' + case 'tileset': + return item.assetType === 'sprite' && item.metaType === 'spritesheet' + case 'background': + return item.assetType === 'background' + case 'ui': + return item.assetType === 'ui' + default: + return true + } +} + export default function AssetGallery() { const [assets, setAssets] = useState([]) const [loading, setLoading] = useState(true) + const [activeCat, setActiveCat] = useState('all') useEffect(() => { getRecentAssets() @@ -20,81 +49,110 @@ export default function AssetGallery() { .finally(() => setLoading(false)) }, []) - if (loading) return null + const filtered = useMemo( + () => assets.filter(item => matchCategory(item, activeCat)), + [assets, activeCat] + ) + if (loading) return null if (assets.length === 0) return null return (
-

最近生成

-
- {assets.map((item, i) => ( -
{ - // navigate to result page — need projectId for URL - // For now just open the image +

最近生成

+ + {/* 分类标签 */} +
+ {CATEGORIES.map(cat => ( +
+ {cat.label} + ))}
+ + {/* 素材网格 */} + {filtered.length === 0 ? ( +

暂无此类素材

+ ) : ( +
+ {filtered.map((item, i) => ( +
+
+ {item.prompt} +
+
+ + {ASSET_TYPE_LABELS[item.assetType] || item.assetType} + +
+

+ {item.prompt} +

+
+ ))} +
+ )}
) }