feat(themes): 添加可编辑的站点外观和皮肤主题(#408)

This commit is contained in:
Ray
2026-09-03 21:13:41 +08:00
committed by GitHub
parent c768fccbc2
commit 5e66cb7c45
70 changed files with 3654 additions and 327 deletions

View File

@@ -84,7 +84,7 @@ const adminNavigation: Array<{ label: string; items: AdminNavigationItem[] }> =
{
label: "系统配置",
items: [
{ path: "/admin/settings/appearance", label: "外观管理", description: "品牌、Logo 与登录影片", icon: <Palette className="size-4" /> },
{ path: "/admin/settings/appearance", label: "站点及外观", description: "品牌、SEO、备案与皮肤", icon: <Palette className="size-4" /> },
{ path: "/admin/settings/features", label: "功能开放", description: "工作台、插件与模型能力", icon: <ToggleLeft className="size-4" /> },
{ path: "/admin/settings/drawing-engine", label: "绘图工具", description: "画布绘图节点默认引擎", icon: <Paintbrush className="size-4" /> },
{ path: "/admin/settings/runtime-policy", label: "资源与策略", description: "配额、并发、频控与超时", icon: <Settings2 className="size-4" /> },
@@ -127,7 +127,7 @@ export function AdminShell() {
};
return (
<ConfigProvider theme={getAdminAntThemeConfig(dark)}>
<ConfigProvider theme={getAdminAntThemeConfig(dark, appearance.activeSkin)}>
<main className="admin-shell app-user-workspace flex h-full min-h-0 overflow-hidden text-foreground">
<aside className={cn("app-workspace-sidebar admin-sidebar hidden shrink-0 flex-col overflow-hidden lg:flex", collapsed && "is-collapsed")}>
<div className="admin-sidebar-identity shrink-0">

View File

@@ -5,12 +5,14 @@ import { useBlocker } from "react-router";
import { cn } from "@/lib/utils";
import { getAdminEmailSetting, updateAdminEmailSetting, type EmailSetting } from "@/services/api/wallet";
import { useAppearanceStore } from "@/stores/use-appearance-store";
import { AdminStatusBadge, configuredSecretText, SettingsSectionCard } from "./admin-ui";
type EmailFormValues = Pick<EmailSetting, "enabled" | "host" | "port" | "username" | "password" | "encryption" | "fromEmail" | "fromName">;
export default function EmailSettingsPanel() {
const { message, modal } = App.useApp();
const brandName = useAppearanceStore((state) => state.appearance.brandName);
const [setting, setSetting] = useState<EmailSetting | null>(null);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
@@ -394,9 +396,9 @@ export default function EmailSettingsPanel() {
validator: (_, value: string | undefined) => (!draftEnabled || !value || !/[\r\n]/.test(value) ? Promise.resolve() : Promise.reject(new Error("发件人名称不能包含换行"))),
},
]}
extra="留空时服务端使用“影策”。"
extra={`留空时自动使用当前站点名称“${brandName}”;之后修改站点名称会同步更新。`}
>
<Input placeholder="影策" />
<Input placeholder={brandName} />
</Form.Item>
</div>
</div>
@@ -429,7 +431,7 @@ function toEmailFormValues(setting: EmailSetting): EmailFormValues {
password: "",
encryption: setting.encryption,
fromEmail: setting.fromEmail,
fromName: setting.fromName,
fromName: setting.fromNameInherited ? "" : setting.fromName,
};
}
@@ -442,7 +444,7 @@ function normalizeEmailFormValues(values: Partial<EmailFormValues>): EmailFormVa
password: values.password?.trim() || "",
encryption: values.encryption === "tls" || values.encryption === "none" ? values.encryption : "starttls",
fromEmail: values.fromEmail?.trim().toLowerCase() || "",
fromName: values.fromName?.trim() || "影策",
fromName: values.fromName?.trim() || "",
};
}
@@ -482,6 +484,7 @@ function isEmailSetting(value: unknown): value is EmailSetting {
["starttls", "tls", "none"].includes(setting.encryption || "") &&
typeof setting.fromEmail === "string" &&
typeof setting.fromName === "string" &&
typeof setting.fromNameInherited === "boolean" &&
typeof setting.hasPassword === "boolean"
);
}