外观设置改为半屏布局;背景图仅保存后生效,并支持媒体库选用。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-20 10:51:20 +08:00
parent e598782279
commit 68e90ff6ff
7 changed files with 953 additions and 481 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
"use client";
import { Images, Loader2 } from "lucide-react";
import { useEffect, useState } from "react";
import Modal from "@/components/Modal";
import { apiMyMedia, type MediaAttachment } from "@/lib/api";
import { mediaURLFitsBackground, type BgSurface } from "@/lib/bg";
const SURFACE_LABEL: Record<BgSurface, string> = {
site: "前台背景",
admin: "后台背景",
};
const BG_MAX_BYTES = 8 * 1024 * 1024;
const BG_MAX_DIM = 8192;
function mediaFitsBackground(item: MediaAttachment): boolean {
if (!mediaURLFitsBackground(item.url)) return false;
if (item.size > 0 && item.size > BG_MAX_BYTES) return false;
if (item.width > 0 && item.width > BG_MAX_DIM) return false;
if (item.height > 0 && item.height > BG_MAX_DIM) return false;
return true;
}
export default function BgMediaPicker({
open,
surface,
onClose,
onPick,
}: {
open: boolean;
surface: BgSurface;
onClose: () => void;
onPick: (item: MediaAttachment) => void;
}) {
const [items, setItems] = useState<MediaAttachment[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
if (!open) return;
let cancelled = false;
setLoading(true);
setError("");
void apiMyMedia()
.then((res) => {
if (cancelled) return;
if (res.error) {
setError(res.error);
setItems([]);
return;
}
const all = res.attachments || [];
setItems(all.filter(mediaFitsBackground));
})
.catch(() => {
if (!cancelled) setError("加载媒体库失败");
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, [open, surface]);
return (
<Modal
open={open}
title={`从媒体库选择 · ${SURFACE_LABEL[surface]}`}
description="只列出 JPEG / PNG / WebP(≤8MB、边长≤8192)。保存后会复制到背景目录,之后删除原图不影响背景。"
onClose={onClose}
maxWidthClass="max-w-lg"
dismissOnOverlay
>
{loading ? (
<p className="meta text-[13px] flex items-center gap-2 py-6 justify-center">
<Loader2 size={14} className="animate-spin" /> 加载中
</p>
) : error ? (
<p className="text-[13px] py-4" style={{ color: "var(--danger, #b42318)" }}>
{error}
</p>
) : items.length === 0 ? (
<p className="meta text-[13px] py-6 text-center">
没有可用图片。请先在发帖或媒体库上传 JPEG / PNG / WebP。
</p>
) : (
<div className="grid grid-cols-4 sm:grid-cols-5 gap-2">
{items.map((item) => (
<button
key={item.id}
type="button"
onClick={() => {
onPick(item);
onClose();
}}
className="relative group aspect-square rounded-xl overflow-hidden"
style={{ boxShadow: "0 0 0 1px var(--line)", background: "var(--panel-2)" }}
data-tip="用作草稿预览,点保存后才生效"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={item.url}
alt={`${SURFACE_LABEL[surface]}候选图`}
className="w-full h-full object-cover"
loading="lazy"
/>
<span
className="pointer-events-none absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100"
style={{ background: "color-mix(in srgb, var(--ink) 40%, transparent)", color: "#fff" }}
>
<Images size={14} />
</span>
</button>
))}
</div>
)}
</Modal>
);
}