From 5461cff1dbda5763649bf16ce2a8fd9730d6146c Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 1 Jun 2026 19:40:22 +0800 Subject: [PATCH] fix: improve PSD template management and clipping masks --- .../psd-templates-settings-client.tsx | 80 ++++++++++++++++++- .../agents/AgentPsdTemplateEditorDialog.tsx | 19 ++++- components/top-navbar.tsx | 4 +- lib/agents/psd-template-renderer.ts | 2 +- 4 files changed, 95 insertions(+), 10 deletions(-) diff --git a/app/(admin)/settings/agent-workflow/psd-templates/psd-templates-settings-client.tsx b/app/(admin)/settings/agent-workflow/psd-templates/psd-templates-settings-client.tsx index e6209e299..ba95ed259 100644 --- a/app/(admin)/settings/agent-workflow/psd-templates/psd-templates-settings-client.tsx +++ b/app/(admin)/settings/agent-workflow/psd-templates/psd-templates-settings-client.tsx @@ -95,6 +95,7 @@ const SYSTEM_FONT_OPTIONS = [ const EMPTY_CATEGORY_SELECT_VALUE = "__none__"; const EMPTY_BRAND_SELECT_VALUE = "__unassigned__"; +const ALL_TEMPLATE_BRANDS_SELECT_VALUE = "__all_template_brands__"; const UNASSIGNED_BRAND_LABEL = "未分类"; const MAX_PSD_TEMPLATE_FILE_SIZE = 80 * 1024 * 1024; const PSD_TEMPLATE_MODE_OPTIONS: Array<{ @@ -366,6 +367,7 @@ export function PsdTemplatesSettingsClient({ const [templateBrandDrafts, setTemplateBrandDrafts] = useState>({}); const [fontBrandDrafts, setFontBrandDrafts] = useState>({}); const [parsingTemplateIds, setParsingTemplateIds] = useState([]); + const [templateBrandFilter, setTemplateBrandFilter] = useState(ALL_TEMPLATE_BRANDS_SELECT_VALUE); const selectedTemplate = useMemo( () => templates.find((template) => template.id === selectedTemplateId) || null, @@ -444,9 +446,42 @@ export function PsdTemplatesSettingsClient({ const currentBrand = normalizeManagedBrandName(draftTemplate?.brandName); return Array.from(new Set([...managedBrandOptions, ...(currentBrand ? [currentBrand] : [])])); }, [draftTemplate?.brandName, managedBrandOptions]); + const templateBrandFilterOptions = useMemo(() => { + const brands = new Set(); + let hasUnassigned = false; + + templates.forEach((template) => { + const brand = normalizeManagedBrandName(template.brandName); + if (brand) { + brands.add(brand); + } else { + hasUnassigned = true; + } + }); + + return { + brands: Array.from(brands).sort((left, right) => left.localeCompare(right, "zh-CN")), + hasUnassigned, + }; + }, [templates]); + const filteredTemplates = useMemo(() => { + if (templateBrandFilter === ALL_TEMPLATE_BRANDS_SELECT_VALUE) { + return templates; + } + + if (templateBrandFilter === EMPTY_BRAND_SELECT_VALUE) { + return templates.filter((template) => !normalizeManagedBrandName(template.brandName)); + } + + return templates.filter( + (template) => + normalizeManagedBrandName(template.brandName).toLowerCase() === + templateBrandFilter.toLowerCase() + ); + }, [templateBrandFilter, templates]); const groupedTemplates = useMemo(() => { const grouped = new Map(); - templates.forEach((template) => { + filteredTemplates.forEach((template) => { const label = normalizeManagedBrandName(template.brandName) || UNASSIGNED_BRAND_LABEL; const current = grouped.get(label) || []; current.push(template); @@ -463,7 +498,7 @@ export function PsdTemplatesSettingsClient({ label, items, })); - }, [templates]); + }, [filteredTemplates]); const groupedFonts = useMemo(() => { const grouped = new Map(); fonts.forEach((font) => { @@ -2960,9 +2995,44 @@ export function PsdTemplatesSettingsClient({ 模板列表 - 按品牌查看模板,选择一个模板继续配置槽位。 + 按品牌筛选模板,选择一个模板继续配置槽位。 +
+
+
品牌筛选
+
+ 当前显示 {filteredTemplates.length} / {templates.length} 个模板 +
+
+
+ + {templateBrandFilter !== ALL_TEMPLATE_BRANDS_SELECT_VALUE ? ( + + ) : null} +
+
{templateListErrorMessage && templates.length > 0 ? (
@@ -3008,6 +3078,10 @@ export function PsdTemplatesSettingsClient({
还没有 PSD 模板,先上传一个模板开始配置。
+ ) : filteredTemplates.length === 0 ? ( +
+ 当前品牌下没有 PSD 模板,可以切换品牌或清除筛选。 +
) : ( groupedTemplates.map((group) => (
diff --git a/components/agents/AgentPsdTemplateEditorDialog.tsx b/components/agents/AgentPsdTemplateEditorDialog.tsx index b628adb3a..31eb87906 100644 --- a/components/agents/AgentPsdTemplateEditorDialog.tsx +++ b/components/agents/AgentPsdTemplateEditorDialog.tsx @@ -391,6 +391,20 @@ function buildPsdLayerPreviewUrl( }); } +function buildPsdLayerMaskUrl(maskUrl: string, layerWidth: number) { + const normalizedUrl = String(maskUrl || "").trim(); + if (!normalizedUrl) { + return ""; + } + + return buildImageProxyUrl(normalizedUrl, { + width: Math.max(96, Math.min(1280, Math.round(layerWidth || 720))), + quality: 100, + format: "png", + delivery: "proxy", + }); +} + function getPsdLayerPreviewSourceUrl( layer: Pick ) { @@ -1482,10 +1496,7 @@ function getAssignedImageMaskStyle( const normalizedMaskUrl = String(layer.maskUrl || fallbackMaskUrl || "").trim(); const hasMask = Boolean(normalizedMaskUrl); const maskImageUrl = hasMask - ? `url(${buildPsdLayerPreviewUrl(normalizedMaskUrl, { - width: layer.width, - quality: 72, - })})` + ? `url(${buildPsdLayerMaskUrl(normalizedMaskUrl, layer.width)})` : undefined; return { WebkitMaskImage: maskImageUrl, diff --git a/components/top-navbar.tsx b/components/top-navbar.tsx index de9fad090..05109385a 100644 --- a/components/top-navbar.tsx +++ b/components/top-navbar.tsx @@ -271,7 +271,7 @@ export function TopNavbar({ }; const navContent = ( -