import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { Button } from "@heroui/button"; import { Card, CardBody, CardHeader } from "@heroui/card"; import { Input } from "@heroui/input"; import { Spinner } from "@heroui/spinner"; import { Divider } from "@heroui/divider"; import { Switch } from "@heroui/switch"; import { Select, SelectItem } from "@heroui/select"; import toast from "react-hot-toast"; import { updateConfigs } from "@/api"; import { SettingsIcon } from "@/components/icons"; import { isAdmin } from "@/utils/auth"; import { getCachedConfigs, clearConfigCache, updateSiteConfig, } from "@/config/site"; // 简单的保存图标组件 const SaveIcon = ({ className }: { className?: string }) => ( ); interface ConfigItem { key: string; label: string; placeholder?: string; description?: string; type: "input" | "switch" | "select"; options?: { label: string; value: string; description?: string }[]; dependsOn?: string; // 依赖的配置项key dependsValue?: string; // 依赖的配置项值 } // 网站配置项定义 const CONFIG_ITEMS: ConfigItem[] = [ { key: "ip", label: "面板后端地址", placeholder: "请输入面板后端IP:PORT", description: "格式“ip:port”,用于对接节点时使用,ip是你安装面板服务器的公网ip,端口是安装脚本内输入的后端端口。不要套CDN,不支持https,通讯数据有加密", type: "input", }, { key: "app_name", label: "应用名称", placeholder: "请输入应用名称", description: "在浏览器标签页和导航栏显示的应用名称", type: "input", }, { key: "captcha_enabled", label: "启用验证码", description: "开启后,用户登录时需要完成验证码验证", type: "switch", }, { key: "captcha_type", label: "验证码类型", description: "选择验证码的显示类型,不同类型有不同的安全级别", type: "select", dependsOn: "captcha_enabled", dependsValue: "true", options: [ { label: "随机类型", value: "RANDOM", description: "系统随机选择验证码类型", }, { label: "滑块验证码", value: "SLIDER", description: "拖动滑块完成拼图验证", }, { label: "文字点选验证码", value: "WORD_IMAGE_CLICK", description: "按顺序点击指定文字", }, { label: "旋转验证码", value: "ROTATE", description: "旋转图片到正确角度", }, { label: "拼图验证码", value: "CONCAT", description: "拖动滑块完成图片拼接", }, ], }, ]; // 初始化时从缓存读取配置,避免闪烁 const getInitialConfigs = (): Record => { if (typeof window === "undefined") return {}; const configKeys = ["app_name", "captcha_enabled", "captcha_type", "ip"]; const initialConfigs: Record = {}; try { configKeys.forEach((key) => { const cachedValue = localStorage.getItem("vite_config_" + key); if (cachedValue) { initialConfigs[key] = cachedValue; } }); } catch (error) {} return initialConfigs; }; export default function ConfigPage() { const navigate = useNavigate(); const initialConfigs = getInitialConfigs(); const [configs, setConfigs] = useState>(initialConfigs); const [loading, setLoading] = useState( Object.keys(initialConfigs).length === 0, ); // 如果有缓存数据,不显示loading const [saving, setSaving] = useState(false); const [hasChanges, setHasChanges] = useState(false); const [originalConfigs, setOriginalConfigs] = useState>(initialConfigs); // 权限检查 useEffect(() => { if (!isAdmin()) { toast.error("权限不足,只有管理员可以访问此页面"); navigate("/dashboard", { replace: true }); return; } }, [navigate]); // 加载配置数据(优先从缓存) const loadConfigs = async (currentConfigs?: Record) => { const configsToCompare = currentConfigs || configs; const hasInitialData = Object.keys(configsToCompare).length > 0; // 如果已有缓存数据,不显示loading,静默更新 if (!hasInitialData) { setLoading(true); } try { const configData = await getCachedConfigs(); // 只有在数据有变化时才更新 const hasDataChanged = JSON.stringify(configData) !== JSON.stringify(configsToCompare); if (hasDataChanged) { setConfigs(configData); setOriginalConfigs({ ...configData }); setHasChanges(false); } else { } } catch (error) { // 只有在没有缓存数据时才显示错误 if (!hasInitialData) { toast.error("加载配置出错,请重试"); } } finally { setLoading(false); } }; useEffect(() => { // 延迟加载,避免阻塞初始渲染 const timer = setTimeout(() => { loadConfigs(initialConfigs); }, 100); return () => clearTimeout(timer); }, []); // 只在组件挂载时执行一次 // 处理配置项变更 const handleConfigChange = (key: string, value: string) => { let newConfigs = { ...configs, [key]: value }; // 特殊处理:启用验证码时,如果验证码类型未设置,默认为随机 if (key === "captcha_enabled" && value === "true") { if (!newConfigs.captcha_type) { newConfigs.captcha_type = "RANDOM"; } } setConfigs(newConfigs); // 检查是否有变更 const hasChangesNow = Object.keys(newConfigs).some( (k) => newConfigs[k] !== originalConfigs[k], ) || Object.keys(originalConfigs).some( (k) => originalConfigs[k] !== newConfigs[k], ); setHasChanges(hasChangesNow); }; // 保存配置 const handleSave = async () => { setSaving(true); try { const response = await updateConfigs(configs); if (response.code === 0) { toast.success("配置保存成功"); // 清除所有配置缓存,强制下次重新获取 clearConfigCache(); // 获取变更的配置项 const changedKeys = Object.keys(configs).filter( (key) => configs[key] !== originalConfigs[key], ); setOriginalConfigs({ ...configs }); setHasChanges(false); // 如果应用名称发生变化,立即更新网站配置 if (changedKeys.includes("app_name")) { await updateSiteConfig(); } // 触发配置更新事件,通知其他组件 window.dispatchEvent( new CustomEvent("configUpdated", { detail: { changedKeys }, }), ); } else { toast.error("保存配置失败: " + response.msg); } } catch (error) { toast.error("保存配置出错,请重试"); } finally { setSaving(false); } }; // 检查配置项是否应该显示(依赖检查) const shouldShowItem = (item: ConfigItem): boolean => { if (!item.dependsOn || !item.dependsValue) { return true; } return configs[item.dependsOn] === item.dependsValue; }; // 渲染不同类型的配置项 const renderConfigItem = (item: ConfigItem) => { const isChanged = hasChanges && configs[item.key] !== originalConfigs[item.key]; switch (item.type) { case "input": return ( handleConfigChange(item.key, e.target.value)} /> ); case "switch": return ( handleConfigChange(item.key, checked ? "true" : "false") } > {configs[item.key] === "true" ? "已启用" : "已禁用"} ); case "select": return ( ); default: return null; } }; if (loading) { return (
); } return (
{/* 页面标题 */}

网站配置

管理网站的基本信息和显示设置

基本设置

配置网站的基本信息,这些设置会影响网站的显示效果

{CONFIG_ITEMS.map((item, index) => { // 检查配置项是否应该显示 if (!shouldShowItem(item)) { return null; } // 计算是否是最后一个显示的项目(用于决定是否显示分隔线) const remainingItems = CONFIG_ITEMS.slice(index + 1).filter( shouldShowItem, ); const isLastItem = remainingItems.length === 0; return (
{item.description && (

{item.description}

)}
{/* 渲染配置项 */} {renderConfigItem(item)} {/* 分隔线 */} {!isLastItem && }
); })}
{/* 操作提示 */} {hasChanges && (
检测到配置变更,请记得保存您的修改
)}
); }