feat(welcome): 站点外观 - 合并欢迎页开放开关与入口分包修复
# Conflicts: # docs/content/docs/progress/pending-test.mdx
This commit is contained in:
@@ -2,6 +2,6 @@ import "@fontsource-variable/inter";
|
||||
import "@fontsource-variable/jetbrains-mono";
|
||||
import { bootstrapAppearance } from "@/services/appearance-bootstrap";
|
||||
|
||||
// Keep the public film page independent of workspace and appearance requests.
|
||||
// The public film entry checks its availability independently of workspace bootstrap.
|
||||
if (/^\/welcome\/?$/.test(window.location.pathname)) void import("./welcome-application");
|
||||
else void bootstrapAppearance().finally(() => import("./application"));
|
||||
|
||||
@@ -368,6 +368,7 @@ function parseFeatureAvailability(value: unknown): FeatureAvailability {
|
||||
if (typeof record[key] !== "boolean") throw new Error("功能开放配置响应缺少有效开关状态");
|
||||
}
|
||||
return {
|
||||
welcomeEnabled: record.welcomeEnabled as boolean,
|
||||
shortDramaEnabled: record.shortDramaEnabled as boolean,
|
||||
taskCenterEnabled: record.taskCenterEnabled as boolean,
|
||||
creditsEnabled: record.creditsEnabled as boolean,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { AdminStatusBadge, SettingsSectionCard } from "@/pages/admin/components/
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cloneSkinDefinition, DEFAULT_CLASSIC_SKIN, duplicateSkinDefinition, normalizeSkinDefinition, type SkinDefinition } from "@/lib/skin-themes";
|
||||
import { SkinThemeEditor } from "@/pages/admin/settings/components/skin-theme-editor";
|
||||
import { WelcomeSetting } from "@/pages/admin/settings/components/welcome-setting";
|
||||
import { deleteAdminResources } from "@/services/api/admin-storage";
|
||||
import { getAdminAppearance, resetAdminAppearance, updateAdminAppearance, uploadAppearanceAsset, type AdminAppearance, type AppearanceAssetSlot } from "@/services/api/appearance";
|
||||
import { commitPublicAppearance, DEFAULT_PUBLIC_APPEARANCE } from "@/stores/use-appearance-store";
|
||||
@@ -473,6 +474,7 @@ export default function AppearanceSettingsPage() {
|
||||
onChange={(event) => setBrandSlug(event.target.value.toLocaleLowerCase().replace(/[^a-z0-9-]/g, ""))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<WelcomeSetting />
|
||||
</Form>
|
||||
<div className="admin-appearance-brand-logo admin-appearance-logo-stack">
|
||||
<AssetPicker
|
||||
|
||||
65
web/src/pages/admin/settings/components/welcome-setting.tsx
Normal file
65
web/src/pages/admin/settings/components/welcome-setting.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { App, Button } from "antd";
|
||||
import { Switch } from "@/components/ui/base/switch";
|
||||
import { getAdminFeatureAvailability, updateAdminFeatureAvailability } from "@/services/api/auth";
|
||||
import { useUserStore } from "@/stores/use-user-store";
|
||||
|
||||
export function WelcomeSetting() {
|
||||
const { message } = App.useApp();
|
||||
const [enabled, setEnabled] = useState<boolean | null>(null);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [revision, setRevision] = useState(0);
|
||||
const setFeatures = useUserStore((state) => state.setFeatures);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
setError("");
|
||||
getAdminFeatureAvailability()
|
||||
.then(({ features }) => {
|
||||
if (typeof features.welcomeEnabled !== "boolean") throw new Error("欢迎页配置无效");
|
||||
if (active) setEnabled(features.welcomeEnabled);
|
||||
})
|
||||
.catch(() => {
|
||||
if (active) setError("读取欢迎页状态失败,请重试。");
|
||||
});
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [revision]);
|
||||
|
||||
async function changeWelcome(value: boolean) {
|
||||
if (saving) return;
|
||||
setSaving(true);
|
||||
try {
|
||||
const { features } = await updateAdminFeatureAvailability({ welcomeEnabled: value });
|
||||
if (features.welcomeEnabled !== value) throw new Error("欢迎页状态未保存,请重试");
|
||||
setEnabled(features.welcomeEnabled);
|
||||
setFeatures(features);
|
||||
message.success(value ? "欢迎页已启用" : "欢迎页已关闭");
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : "保存欢迎页状态失败");
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="admin-appearance-logo-frame-option">
|
||||
<div className="admin-appearance-logo-frame-copy">
|
||||
<strong>启用欢迎页</strong>
|
||||
<p>切换后立即保存。关闭后访问 /welcome 将跳转到首页。</p>
|
||||
{error ? (
|
||||
<div role="alert">
|
||||
{error}
|
||||
<Button onClick={() => setRevision((value) => value + 1)}>重试</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="admin-appearance-logo-frame-control">
|
||||
<span>{enabled === null ? "读取中" : saving ? "保存中" : enabled ? "已启用" : "已关闭"}</span>
|
||||
<Switch aria-label="启用欢迎页" checked={enabled === true} disabled={enabled === null || saving || Boolean(error)} onChange={(value) => void changeWelcome(value)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -396,7 +396,7 @@ export function getAdminFeatureAvailability() {
|
||||
return http.get<{ features: FeatureAvailability }>("/admin/settings/features");
|
||||
}
|
||||
|
||||
export function updateAdminFeatureAvailability(features: Pick<FeatureAvailability, "shortDramaEnabled" | "taskCenterEnabled" | "creditsEnabled" | "customChannelsEnabled" | "frontendModelsEnabled" | "pluginCenterEnabled" | "systemPluginsVisibleToUsers">) {
|
||||
export function updateAdminFeatureAvailability(features: Partial<Pick<FeatureAvailability, "welcomeEnabled" | "shortDramaEnabled" | "taskCenterEnabled" | "creditsEnabled" | "customChannelsEnabled" | "frontendModelsEnabled" | "pluginCenterEnabled" | "systemPluginsVisibleToUsers">>) {
|
||||
return http.patch<{ features: FeatureAvailability }>("/admin/settings/features", features);
|
||||
}
|
||||
|
||||
|
||||
5
web/src/services/api/welcome.ts
Normal file
5
web/src/services/api/welcome.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { http } from "./request";
|
||||
|
||||
export function getWelcomeAvailability() {
|
||||
return http.get<{ welcomeEnabled: boolean }>("/public/welcome");
|
||||
}
|
||||
@@ -25,6 +25,7 @@ export type RuntimeLimits = {
|
||||
};
|
||||
|
||||
export type FeatureAvailability = {
|
||||
welcomeEnabled: boolean;
|
||||
shortDramaEnabled: boolean;
|
||||
taskCenterEnabled: boolean;
|
||||
creditsEnabled: boolean;
|
||||
@@ -38,6 +39,7 @@ export type FeatureAvailability = {
|
||||
};
|
||||
|
||||
export const defaultFeatureAvailability: FeatureAvailability = {
|
||||
welcomeEnabled: true,
|
||||
shortDramaEnabled: true,
|
||||
taskCenterEnabled: true,
|
||||
creditsEnabled: true,
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { getWelcomeAvailability } from "@/services/api/welcome";
|
||||
import WelcomePage from "@/pages/welcome";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<StrictMode><WelcomePage /></StrictMode>);
|
||||
async function renderWelcome() {
|
||||
try {
|
||||
const { welcomeEnabled } = await getWelcomeAvailability();
|
||||
if (welcomeEnabled !== true) {
|
||||
window.location.replace("/");
|
||||
return;
|
||||
}
|
||||
createRoot(document.getElementById("root")!).render(<StrictMode><WelcomePage /></StrictMode>);
|
||||
} catch (error) {
|
||||
console.error("Welcome page initialization failed", error);
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<main role="alert">
|
||||
<p>暂时无法打开欢迎页,请稍后重试。</p>
|
||||
<button onClick={() => window.location.reload()}>重试</button>
|
||||
<a href="/">返回首页</a>
|
||||
</main>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void renderWelcome();
|
||||
|
||||
@@ -45,6 +45,14 @@ export default defineConfig({
|
||||
includeDependenciesRecursively: false,
|
||||
minSize: 40 * 1024,
|
||||
groups: [
|
||||
{
|
||||
// Shared interop helpers must not be emitted into a route entry:
|
||||
// AntD would import that entry back and execute its bootstrap early.
|
||||
name: "vendor-babel-runtime",
|
||||
minSize: 0,
|
||||
test: /node_modules[\\/]@babel[\\/]runtime[\\/]/,
|
||||
priority: 40,
|
||||
},
|
||||
{
|
||||
name: "vendor-react",
|
||||
test: /node_modules[\\/](?:react(?:-dom|-router|-router-dom)?|scheduler|zustand|use-sync-external-store|@tanstack[\\/](?:query-core|react-query))[\\/]/,
|
||||
|
||||
Reference in New Issue
Block a user