后台站点设计UIUX优化

This commit is contained in:
2026-09-19 04:14:13 +08:00
parent a8efb9ecef
commit 2b3d122e56
24 changed files with 2952 additions and 306 deletions

View File

@@ -1,6 +1,21 @@
import {
normalizeBrandLogoFit,
normalizeBrandLogoSize,
normalizeBrandMark,
normalizeBrandURL,
normalizeFooterLinks,
normalizeKeywords,
type BrandLogoFit,
type BrandLogoSize,
type BrandMark,
type BrandSlot,
type FooterLink,
} from "./brand";
import { CSRF_COOKIE } from "./cookies";
import { emitForceLogout } from "./userEvents";
export type { FooterLink };
// API 基础配置
// 注意:客户端请求使用相对路径 /api/*,走 Next.js rewrite 代理到后端,
// 这样浏览器视为同源,Cookie 自动携带,无需处理 CORS。
@@ -647,6 +662,21 @@ export interface PublicSettings {
trust_reviewed_publish: boolean;
site_name: string;
site_description: string;
/** 无 Logo 时页眉字标;空则回退显示名称 */
site_wordmark: string;
/** 浏览器标题后缀与页眉副标题;空则不显示 */
site_slogan: string;
site_keywords: string[];
logo_light_url: string;
logo_dark_url: string;
favicon_url: string;
/** 页眉形式:image_text 图加文字 / image 纯图 / text 纯文字 */
brand_mark: BrandMark;
/** 图槽画幅 */
brand_logo_size: BrandLogoSize;
/** 图槽铺法:cover 填充 / contain 适应 / repeat 平铺 / stretch 拉伸 */
brand_logo_fit: BrandLogoFit;
footer_links: FooterLink[];
allow_register: boolean;
/** 全站是否开放评论;关闭后前台不展示评论能力 */
allow_comments: boolean;
@@ -703,6 +733,16 @@ const DEFAULT_PUBLIC_SETTINGS: PublicSettings = {
trust_reviewed_publish: true,
site_name: "姜十三论坛",
site_description: "姜十三论坛 - 技术分享与讨论社区",
site_wordmark: "",
site_slogan: "",
site_keywords: [],
logo_light_url: "",
logo_dark_url: "",
favicon_url: "",
brand_mark: "image_text",
brand_logo_size: "sq",
brand_logo_fit: "contain",
footer_links: [],
allow_register: true,
allow_comments: true,
allow_messages: true,
@@ -765,6 +805,16 @@ function normalizePublicSettings(data: Partial<PublicSettings> | null | undefine
data?.site_description !== undefined
? String(data.site_description).trim()
: DEFAULT_PUBLIC_SETTINGS.site_description,
site_wordmark: String(data?.site_wordmark ?? "").trim(),
site_slogan: String(data?.site_slogan ?? "").trim(),
site_keywords: normalizeKeywords(data?.site_keywords),
logo_light_url: normalizeBrandURL("logo_light", data?.logo_light_url),
logo_dark_url: normalizeBrandURL("logo_dark", data?.logo_dark_url),
favicon_url: normalizeBrandURL("favicon", data?.favicon_url),
brand_mark: normalizeBrandMark(data?.brand_mark),
brand_logo_size: normalizeBrandLogoSize(data?.brand_logo_size),
brand_logo_fit: normalizeBrandLogoFit(data?.brand_logo_fit),
footer_links: normalizeFooterLinks(data?.footer_links),
allow_register: data?.allow_register !== false,
allow_comments: data?.allow_comments !== false,
allow_messages: data?.allow_messages !== false,
@@ -2141,6 +2191,16 @@ export type UpdateSiteSettingsBody = {
trust_reviewed_publish?: boolean;
site_name?: string;
site_description?: string;
site_wordmark?: string;
site_slogan?: string;
site_keywords?: string[];
logo_light_url?: string;
logo_dark_url?: string;
favicon_url?: string;
brand_mark?: BrandMark;
brand_logo_size?: BrandLogoSize;
brand_logo_fit?: BrandLogoFit;
footer_links?: FooterLink[];
allow_register?: boolean;
allow_comments?: boolean;
allow_messages?: boolean;
@@ -2176,6 +2236,37 @@ export async function apiUpdateSiteSettings(body: UpdateSiteSettingsBody): Promi
return normalizePublicSettings(data as Partial<PublicSettings>);
}
/** 上传品牌图,只落盘返回 URL,不写入站点设置 */
export async function apiUploadBrand(slot: BrandSlot, file: File): Promise<string> {
const form = new FormData();
form.append("slot", slot);
form.append("file", file, file.name || "brand.png");
const res = await fetchWithRefresh("/api/admin/upload/brand", {
method: "POST",
headers: clientHeaders(),
body: form,
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.error || "上传品牌图失败");
const url = String(data.url || "");
if (!url) throw new Error("上传品牌图失败");
return url;
}
/** 把本人媒体库图片复制进品牌目录,不写入站点设置 */
export async function apiBrandFromMedia(slot: BrandSlot, attachmentId: number): Promise<string> {
const res = await fetchWithRefresh("/api/admin/upload/brand/from-media", {
method: "POST",
headers: clientHeaders({ "Content-Type": "application/json" }),
body: JSON.stringify({ attachment_id: attachmentId, slot }),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.error || "选用媒体库图片失败");
const url = String(data.url || "");
if (!url) throw new Error("选用媒体库图片失败");
return url;
}
/** 上传站点背景并立即绑定前台或后台 */
export async function apiUploadSiteBackground(
surface: "site" | "admin",