Files
MeBox/web/src/pages/PosterWallPage.tsx
T
Kiro 778bf77437 feat: backup, notifier, organizer, douban, watch-history, poster-wall
Complete the port of the remaining MediaStation features:

Backend services:
  - service/backup.go: SQLite hot backup via VACUUM INTO, list, delete,
    restore. Backup files stored under {data_dir}/backups/ with timestamps.
  - service/notifier.go: Multi-channel push notifications (Telegram /
    Bark / WeChat Server酱 / Webhook). Configuration via settings table
    keys notify.telegram.*, notify.bark.*, etc.
  - service/organizer.go: Auto-rename + move media files into library
    directory structure. Movie: {Title} ({Year})/{Title} ({Year}).ext,
    TV: {Title}/Season XX/{Title} - SxxExx.ext. Both per-media and
    per-library batch endpoints.
  - service/douban.go: Douban (豆瓣) metadata provider using the
    unofficial subject_suggest API. Returns Chinese titles + posters.
    Requires douban_cookie in secrets config.

Handlers:
  - handler/backup.go: GET/POST/DELETE /admin/backups, POST restore.
  - handler/organizer.go: POST /admin/media/:id/organize,
    POST /admin/libraries/:id/organize.
  - handler/notify.go: POST /admin/notify/test.

Frontend pages:
  - WatchHistoryPage.tsx: full paginated history with progress bars,
    resume buttons, poster thumbnails and timestamps.
  - PosterWallPage.tsx: dense 8-column poster grid of all media
    (matches the original MediaStation PosterWallView.vue).

Sidebar additions: 观看历史 + 海报墙 links in the main nav group.

Verified: go build, go vet, go test all pass; tsc -b && vite build
emits 30 lazy chunks; main bundle 254 KB / 85 KB gzipped.
2026-05-15 14:17:26 +00:00

46 lines
1.5 KiB
TypeScript

import { useEffect, useState } from 'react'
import { GalleryHorizontalEnd } from 'lucide-react'
import { mediaAPI } from '../api/library'
import { MediaCard } from '../components/MediaCard'
import type { Media } from '../types'
// PosterWallPage shows a dense poster grid of all media across every
// library — a visual "poster wall" that matches the original MediaStation's
// PosterWallView.vue. Loads the first 200 items sorted by rating.
export function PosterWallPage() {
const [items, setItems] = useState<Media[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
mediaAPI
.search('', 200)
.then((d) => setItems(d.items))
.finally(() => setLoading(false))
}, [])
return (
<div className="space-y-6">
<header className="flex items-center gap-3">
<GalleryHorizontalEnd className="h-6 w-6 text-primary-400" />
<div>
<h1 className="font-display text-3xl font-bold text-white">海报墙</h1>
<p className="text-sm text-slate-400">
全部媒体的海报展示(最多 200 项)。
</p>
</div>
</header>
{loading && <p className="text-slate-500">加载中…</p>}
{!loading && items.length === 0 && (
<p className="text-slate-400">暂无媒体。请先添加媒体库并扫描。</p>
)}
<div className="grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-8">
{items.map((m) => (
<MediaCard key={m.id} media={m} />
))}
</div>
</div>
)
}