fix: improve project brief preview layout

This commit is contained in:
2026-05-20 16:48:16 +08:00
parent b6f7a44812
commit 8d4275ff56
3 changed files with 191 additions and 22 deletions

View File

@@ -1545,6 +1545,34 @@
"message": "auto-save 2026-05-20 14:45 (~4)",
"hash": "527ccfa",
"files_changed": 4
},
{
"ts": "2026-05-20T16:33:38+08:00",
"type": "commit",
"message": "auto-save 2026-05-20 16:33 (~2)",
"hash": "9b1833c",
"files_changed": 2
},
{
"ts": "2026-05-20T16:39:05+08:00",
"type": "commit",
"message": "auto-save 2026-05-20 16:39 (~3)",
"hash": "e2465d8",
"files_changed": 3
},
{
"ts": "2026-05-20T16:44:32+08:00",
"type": "commit",
"message": "auto-save 2026-05-20 16:44 (~2)",
"hash": "3b04b72",
"files_changed": 2
},
{
"ts": "2026-05-20T16:48:16+08:00",
"type": "commit",
"message": "fix: improve project brief preview layout",
"hash": "61e597c",
"files_changed": 3
}
]
}

View File

@@ -479,7 +479,7 @@ input, textarea {
.project-board-grid {
display: grid;
grid-template-columns: minmax(250px, 310px) minmax(0, 1fr) 88px;
grid-template-columns: clamp(330px, 22vw, 410px) minmax(0, 1fr) 88px;
gap: 14px;
min-height: 0;
flex: 1;
@@ -499,18 +499,90 @@ input, textarea {
padding: 16px;
}
.project-primary-card {
position: relative;
border-radius: 8px;
background:
linear-gradient(180deg, rgba(255,255,255,0.08), rgba(255,255,255,0.028)),
rgba(0,0,0,0.18);
box-shadow:
0 20px 60px -40px rgba(230,245,120,0.72),
inset 0 0 0 1px rgba(255,255,255,0.08);
padding: 9px;
}
.project-primary-preview {
position: relative;
display: grid;
place-items: center;
overflow: hidden;
border-radius: 6px;
background:
linear-gradient(135deg, rgba(255,255,255,0.98), rgba(238,242,230,0.93));
aspect-ratio: 1 / 1;
min-height: 260px;
max-height: min(42vh, 460px);
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.52);
}
.project-primary-image {
width: 100%;
height: 100%;
object-fit: contain;
transform: scale(0.98);
transition: transform 200ms ease, filter 200ms ease;
}
.project-primary-card:hover .project-primary-image {
filter: contrast(1.02) saturate(1.02);
transform: scale(1.015);
}
.project-primary-meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 9px 2px 1px;
}
.project-image-popover {
position: fixed;
z-index: 120;
top: 50%;
left: 50%;
width: min(76vw, 920px);
height: min(82vh, 780px);
display: grid;
place-items: center;
border: 1px solid rgba(255,255,255,0.16);
border-radius: 8px;
background: rgba(255,255,255,0.92);
aspect-ratio: 4 / 3;
min-height: 190px;
box-shadow:
0 20px 60px -38px rgba(230,245,120,0.7),
inset 0 0 0 1px rgba(255,255,255,0.36);
background:
linear-gradient(135deg, rgba(255,255,255,0.10), rgba(255,255,255,0.035)),
rgba(9, 10, 8, 0.86);
box-shadow: 0 30px 120px -28px rgba(0,0,0,0.94);
opacity: 0;
overflow: hidden;
pointer-events: none;
transform: translate(-50%, -48%) scale(0.96);
transition: opacity 180ms ease, transform 180ms ease;
visibility: hidden;
}
.project-image-popover--open {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
visibility: visible;
}
.project-image-popover img {
position: absolute;
inset: 18px;
width: calc(100% - 36px);
height: calc(100% - 36px);
object-fit: contain;
border-radius: 6px;
background: rgba(255,255,255,0.96);
}
.project-stat {
@@ -534,6 +606,31 @@ input, textarea {
padding: 13px;
}
.project-spec-row {
display: grid;
grid-template-columns: 42px minmax(0, 1fr);
gap: 10px;
align-items: start;
border-top: 1px solid rgba(255,255,255,0.06);
padding-top: 8px;
}
.project-spec-row:first-child {
border-top: 0;
padding-top: 0;
}
.project-spec-label {
color: rgba(255,255,255,0.36);
line-height: 1.55;
}
.project-spec-value {
color: rgba(255,255,255,0.78);
line-height: 1.55;
overflow-wrap: anywhere;
}
.project-production-panel {
min-height: 0;
overflow: hidden;
@@ -642,11 +739,22 @@ input, textarea {
}
.project-board-grid {
grid-template-columns: minmax(280px, 340px) minmax(0, 1fr) 96px;
grid-template-columns: clamp(370px, 22vw, 440px) minmax(0, 1fr) 96px;
gap: 18px;
}
}
@media (prefers-reduced-motion: reduce) {
.project-primary-image,
.project-image-popover {
transition: none;
}
.project-primary-card:hover .project-primary-image {
transform: none;
}
}
@media (max-width: 1180px) {
.project-shell {
overflow: auto;
@@ -677,6 +785,14 @@ input, textarea {
overflow: visible;
}
.project-primary-preview {
min-height: 220px;
}
.project-image-popover {
display: none;
}
.session-workspace {
height: auto;
min-height: 0;

View File

@@ -1,6 +1,7 @@
'use client';
import { useCallback, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import PromptPanel from '@/components/PromptPanel';
import Sidebar from '@/components/Sidebar';
import PackPanel from '@/components/PackPanel';
@@ -55,6 +56,18 @@ function ProjectStat({ label, value, tone }: { label: string; value: string | nu
);
}
function ProjectSpecField({ label, value }: { label: string; value?: string | string[] }) {
const text = Array.isArray(value) ? value.filter(Boolean).join('、') : value;
if (!text) return null;
return (
<div className="project-spec-row">
<span className="project-spec-label">{label}</span>
<span className="project-spec-value">{text}</span>
</div>
);
}
function ReferenceStrip({ session }: { session: GenSession }) {
const refs = imageSourcesForSession(session);
if (!refs.length) return null;
@@ -76,6 +89,7 @@ function ProjectBrief({ session }: { session: GenSession }) {
const primaryImage = selectedImages[0] ?? session.images[0] ?? null;
const generatedAssets = (session.packs ?? []).reduce((sum, pack) => sum + pack.assets.length, 0);
const totalSlots = packSlotTotal();
const [previewOpen, setPreviewOpen] = useState(false);
return (
<section className="project-brief-panel">
@@ -93,12 +107,26 @@ function ProjectBrief({ session }: { session: GenSession }) {
</div>
{primaryImage && (
<div className="project-primary-preview mt-5">
<img src={primaryImage.url} alt="当前主方案" className="h-full w-full object-contain" />
<div className="absolute inset-x-0 bottom-0 flex items-center justify-between gap-3 bg-gradient-to-t from-black/72 to-transparent p-3">
<div
className="project-primary-card mt-5"
onMouseEnter={() => setPreviewOpen(true)}
onMouseLeave={() => setPreviewOpen(false)}
onFocus={() => setPreviewOpen(true)}
onBlur={() => setPreviewOpen(false)}
>
<div className="project-primary-preview">
<img src={primaryImage.url} alt="当前主方案" className="project-primary-image" />
</div>
<div className="project-primary-meta">
<span className="text-[10px] font-semibold uppercase tracking-[0.16em] text-white/70">Primary</span>
<span className="text-[10px] text-white/50">{primaryImage.status === 'selected' ? '已选中' : '待筛选'}</span>
</div>
{previewOpen && typeof document !== 'undefined' && createPortal(
<div className="project-image-popover project-image-popover--open" aria-hidden="true">
<img src={primaryImage.url} alt="" />
</div>,
document.body,
)}
</div>
)}
@@ -115,17 +143,14 @@ function ProjectBrief({ session }: { session: GenSession }) {
<span className="text-[10px] text-[#e6f578]/70"></span>
</div>
<div className="mt-3 space-y-2 text-[11px]">
{[
['形态', session.characterSpec.speciesShape],
['比例', session.characterSpec.bodyRatio],
['配色', session.characterSpec.colorPalette.join('、')],
['材料', session.characterSpec.materials.join('、')],
].map(([label, value]) => (
<div key={label} className="grid grid-cols-[42px_minmax(0,1fr)] gap-2">
<span className="text-white/35">{label}</span>
<span className="truncate text-white/75">{value}</span>
</div>
))}
<ProjectSpecField label="形态" value={session.characterSpec.speciesShape} />
<ProjectSpecField label="比例" value={session.characterSpec.bodyRatio} />
<ProjectSpecField label="五官" value={session.characterSpec.faceFeatures} />
<ProjectSpecField label="配色" value={session.characterSpec.colorPalette} />
<ProjectSpecField label="材料" value={session.characterSpec.materials} />
<ProjectSpecField label="配件" value={session.characterSpec.accessories} />
<ProjectSpecField label="识别" value={session.characterSpec.signatureElements} />
<ProjectSpecField label="打样" value={session.characterSpec.manufacturingNotes} />
</div>
</div>
) : (