feat(web): 作品详情头图改为剧照铺顶渐隐到页面底色(方案 A)

- MediaHero 去掉深色圆角卡片:剧照铺在内容区顶部,上下与右缘渐隐成透明;左侧文字区压近乎不透明的页面底色、右侧露出剧照,窄屏改为自上而下压底色;文字用常规前景色,日夜同一结构;没有背景图时不渲染剧照层
- 新增材质参数 --mat-hero-*(浅 / 深两套)与 .media-hero-backdrop
- 「正常」/ Emby / 库存等徽标并进「连载中」所在的事实行
- 操作按钮(订阅 / 搜索资源 / 查看订阅 / 合并作品 / 插槽入口如补字幕 / 命名覆盖)移进头图区外链行下方,库作品页与 TMDB 详情页一致
- 订阅改为主色实心(Button variant="solid",.pill-solid / --mat-pill-solid-*),作为头图区唯一主操作;设计文档记为例外
- 简介折叠:去掉省略号 + 单独一行「展开」的重复,改为最后一行行尾渐隐、「展开」压在渐隐处;按实际排版测量是否需要折叠
- 补 MediaHero / ClampText 渲染测试

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-07 01:44:32 +08:00
1 parent 6549ab92c3
commit caf5a1f2f4
8 files changed
+370 -118

No files matched your search

+46 -34
View File
@@ -1117,7 +1117,7 @@ export default function LibraryDetailPage() {
const back = (
<Link
to={consolePath("library")}
className="mb-4 inline-flex items-center gap-1 text-caption text-muted-foreground hover:text-foreground"
className="relative z-10 mb-4 inline-flex items-center gap-1 text-caption text-muted-foreground hover:text-foreground"
>
<ArrowLeft className="h-4 w-4" />
媒体库
@@ -1175,42 +1175,54 @@ export default function LibraryDetailPage() {
{(ms.data?.items.length ?? 0) > 0 ? <EmbyBadge refOf={{ mediaEntityId: e.id }} /> : null}
</>
}
/>
<div className="mt-4 mb-5 flex flex-wrap items-center gap-2">
{e.status !== "merged" ? (
actions={
<>
<Button size="sm" onClick={() => setSubscribing(true)}>
<CalendarPlus />
订阅
</Button>
<Button variant="outline" size="sm" onClick={() => launcher.open({ title: e.title, ref: `local:${e.id}` })}>
<PackageSearch />
搜索资源
{e.status !== "merged" ? (
<>
{/* 头图区唯一的主操作:实心;其余保持描边 */}
<Button variant="solid" size="sm" onClick={() => setSubscribing(true)}>
<CalendarPlus />
订阅
</Button>
<Button
variant="outline"
size="sm"
onClick={() => launcher.open({ title: e.title, ref: `local:${e.id}` })}
>
<PackageSearch />
搜索资源
</Button>
</>
) : null}
<Button asChild variant="outline" size="sm">
<Link to={`${consolePath("subscriptions")}?mediaEntityId=${e.id}`}>
<CalendarClock />
查看订阅
</Link>
</Button>
{e.status !== "merged" ? (
<Button variant="outline" size="sm" onClick={() => setMerging(true)}>
<GitMerge />
合并作品
</Button>
) : null}
{/* 作品页操作区插槽(ADR-006):补字幕等功能模块的入口 */}
{entityActions.items({ entity: slotEntity }).map((a) => (
<Button
key={a.id}
variant="outline"
size="sm"
onClick={() => entityActions.open(a, { entity: slotEntity })}
>
{a.icon ? <a.icon /> : null}
{a.label}
</Button>
))}
<EntityNamingButton entityId={e.id} kind={e.kind} />
</>
) : null}
<Button asChild variant="outline" size="sm">
<Link to={`${consolePath("subscriptions")}?mediaEntityId=${e.id}`}>
<CalendarClock />
查看订阅
</Link>
</Button>
{e.status !== "merged" ? (
<Button variant="outline" size="sm" onClick={() => setMerging(true)}>
<GitMerge />
合并作品
</Button>
) : null}
{/* 作品页操作区插槽(ADR-006):补字幕等功能模块的入口 */}
{entityActions.items({ entity: slotEntity }).map((a) => (
<Button key={a.id} variant="outline" size="sm" onClick={() => entityActions.open(a, { entity: slotEntity })}>
{a.icon ? <a.icon /> : null}
{a.label}
</Button>
))}
<EntityNamingButton entityId={e.id} kind={e.kind} />
</div>
<TechInfo className="-mt-3 mb-5" items={[{ label: "作品 ID", value: e.id, wide: true }]} />
}
/>
<TechInfo className="mt-5 mb-5" items={[{ label: "作品 ID", value: e.id, wide: true }]} />
<div className="space-y-5">
{credits.cast.length || credits.crew.length ? (
@@ -41,7 +41,7 @@ export default function TmdbMediaPage() {
<button
type="button"
onClick={() => navigate(-1)}
className="mb-4 inline-flex cursor-pointer items-center gap-1 text-caption text-muted-foreground hover:text-foreground"
className="relative z-10 mb-4 inline-flex cursor-pointer items-center gap-1 text-caption text-muted-foreground hover:text-foreground"
>
<ArrowLeft className="h-4 w-4" />
返回
@@ -128,32 +128,38 @@ function Detail({ d }: { d: TmdbMediaDetail }) {
return (
<>
<MediaHero facts={facts} badges={<InventoryBadges inv={d.inventory} />} />
<div className="mt-4 mb-5 flex flex-wrap gap-2">
<Button onClick={() => setSubscribing(true)}>
<CalendarPlus />
订阅
</Button>
<Button variant="outline" onClick={() => launcher.open({ title: d.title, ref })}>
<PackageSearch />
搜索资源
</Button>
{d.localEntityId ? (
<Button asChild variant="outline">
<Link to={consolePath("library", d.localEntityId)}>
<BookmarkPlus />
媒体库详情
</Link>
</Button>
) : (
<Button variant="outline" onClick={createEntity} disabled={entity.isPending}>
<FolderPlus />
{entity.isPending ? "加入中…" : "加入媒体库"}
</Button>
)}
</div>
<MediaHero
facts={facts}
badges={<InventoryBadges inv={d.inventory} />}
actions={
<>
{/* 头图区唯一的主操作:实心;其余保持描边 */}
<Button variant="solid" size="sm" onClick={() => setSubscribing(true)}>
<CalendarPlus />
订阅
</Button>
<Button variant="outline" size="sm" onClick={() => launcher.open({ title: d.title, ref })}>
<PackageSearch />
搜索资源
</Button>
{d.localEntityId ? (
<Button asChild variant="outline" size="sm">
<Link to={consolePath("library", d.localEntityId)}>
<BookmarkPlus />
媒体库详情
</Link>
</Button>
) : (
<Button variant="outline" size="sm" onClick={createEntity} disabled={entity.isPending}>
<FolderPlus />
{entity.isPending ? "加入中…" : "加入媒体库"}
</Button>
)}
</>
}
/>
<div className="space-y-5">
<div className="mt-8 space-y-5">
{d.cast?.length || d.crew?.length ? (
<Panel title="主创">
<CreditsStrip cast={d.cast ?? []} crew={d.crew ?? []} />