mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-01 03:56:38 +08:00
778bf77437
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.
46 lines
1.5 KiB
TypeScript
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>
|
|
)
|
|
}
|