import React, { createContext, useEffect, useState } from "react"; import { DEFAULT_CONFIG } from "app/env"; import { HttpClient } from "core/http"; import { ThemeModeToggle } from "hooks/ThemeMode"; import { Theme, Button, Select, Flex, Container, Heading, Text, Box, TextField, } from "@radix-ui/themes"; import { toast } from "hooks/Notification"; import { Echoes } from "hooks/Echoes"; import { ModuleManager } from "core/moulde"; interface SetupContextType { currentStep: number; setCurrentStep: (step: number) => void; } const SetupContext = createContext({ currentStep: 1, setCurrentStep: () => {}, }); // 步骤组件的通用属性接口 interface StepProps { onNext: () => void; } const StepContainer: React.FC<{ title: string; children: React.ReactNode }> = ({ title, children, }) => ( {title} {children} ); // 通用的导航按钮组件 const NavigationButtons: React.FC< StepProps & { loading?: boolean; disabled?: boolean } > = ({ onNext, loading = false, disabled = false }) => ( ); // 修改输入框组件 const InputField: React.FC<{ label: string; name: string; defaultValue?: string | number; hint?: string; required?: boolean; }> = ({ label, name, defaultValue, hint, required = true }) => ( {label} {required && *} {hint && ( {hint} )} ); const Introduction: React.FC = ({ onNext }) => ( 欢迎使用 Echoes ); // 新增工具函数 const updateEnvConfig = async (newValues: Record) => { const http = HttpClient.getInstance(); // 获取所有 VITE_ 开头的环境变量 let oldEnv = import.meta.env ?? DEFAULT_CONFIG; const viteEnv = Object.entries(oldEnv).reduce( (acc, [key, value]) => { if (key.startsWith("VITE_")) { acc[key] = value; } return acc; }, {} as Record, ); const newEnv = { ...viteEnv, ...newValues, }; await http.dev("/env", { method: "POST", body: JSON.stringify(newEnv), }); Object.assign(import.meta.env, newEnv); }; // 新增表单数据收集函数 const getFormData = (fields: string[]) => { return fields.reduce((acc, field) => { const input = document.querySelector(`[name="${field}"]`) as HTMLInputElement; if (input) { acc[field] = input.value.trim(); } return acc; }, {} as Record); }; const DatabaseConfig: React.FC = ({ onNext }) => { const [dbType, setDbType] = useState("postgresql"); const [loading, setLoading] = useState(false); const http = HttpClient.getInstance(); const validateForm = () => { const getRequiredFields = () => { switch (dbType) { case "sqllite": return ["db_prefix", "db_name"]; case "postgresql": case "mysql": return [ "db_host", "db_prefix", "db_port", "db_user", "db_password", "db_name", ]; default: return []; } }; const requiredFields = getRequiredFields(); const emptyFields: string[] = []; requiredFields.forEach((field) => { const input = document.querySelector( `[name="${field}"]`, ) as HTMLInputElement; if (input && (!input.value || input.value.trim() === "")) { emptyFields.push(field); } }); if (emptyFields.length > 0) { const fieldNames = emptyFields.map((field) => { switch (field) { case "db_host": return "数据库地址"; case "db_prefix": return "数据库前缀"; case "db_port": return "端口"; case "db_user": return "用户名"; case "db_password": return "密码"; case "db_name": return "数据库名"; default: return field; } }); toast.error(`请填写以下必填项:${fieldNames.join("、")}`); return false; } return true; }; const handleNext = async () => { const validation = validateForm(); if (validation !== true) { return; } setLoading(true); try { const formFields = getFormData([ "db_host", "db_prefix", "db_port", "db_user", "db_password", "db_name", ]); const formData = { db_type: dbType, host: formFields?.db_host ?? "localhost", db_prefix: formFields?.db_prefix ?? "echoec_", port:Number(formFields?.db_port?? 0), user: formFields?.db_user ?? "", password: formFields?.db_password ?? "", db_name: formFields?.db_name ?? "", }; await http.post("/sql", formData); toast.success("数据库配置成功!"); setTimeout(() => onNext(), 1000); } catch (error: any) { console.error(error); toast.error(error.title, error.message); } finally { setLoading(false); } }; return (
数据库类型 * PostgreSQL MySQL SQLite {dbType === "postgresql" && ( <> )} {dbType === "mysql" && ( <> )} {dbType === "sqllite" && ( <> )}
); }; interface InstallReplyData { token: string; username: string; password: string; } const AdminConfig: React.FC = ({ onNext }) => { const [loading, setLoading] = useState(false); const http = HttpClient.getInstance(); const handleNext = async () => { setLoading(true); try { const formData = getFormData([ 'admin_username', 'admin_password', 'admin_email', ]); // 添加非空验证 if (!formData.admin_username || !formData.admin_password || !formData.admin_email) { toast.error('请填写所有必填字段'); return; } // 调整数据格式以匹配后端期望的格式 const requestData = { username: formData.admin_username, password: formData.admin_password, email: formData.admin_email, }; const response = (await http.post("/administrator", requestData)) as InstallReplyData; const { token, username, password } = response; localStorage.setItem("token", token); await updateEnvConfig({ VITE_API_USERNAME: username, VITE_API_PASSWORD: password, }); toast.success("管理员账号创建成功!"); onNext(); } catch (error: any) { console.error(error); toast.error(error.title, error.message); } finally { setLoading(false); } }; return (
); }; const SetupComplete: React.FC = () => { useEffect(() => { // 添加延迟后刷新页面 const timer = setTimeout(() => { window.location.reload(); }, 3000); return () => clearTimeout(timer); }, []); return ( 恭喜!安装已完成 系统正在重启中,请稍候... ); }; export default function SetupPage() { const [moduleManager, setModuleManager] = useState(null); const [currentStep, setCurrentStep] = useState(1); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const initManager = async () => { try { const manager = await ModuleManager.getInstance(); setModuleManager(manager); // 确保初始步骤至少从1开始 setCurrentStep(Math.max(manager.getStep() + 1, 1)); } catch (error) { console.error('Init manager error:', error); } finally { setIsLoading(false); } }; initManager(); }, []); const handleStepChange = async (step: number) => { if (moduleManager) { await moduleManager.setStep(step - 1); setCurrentStep(step); } }; if (isLoading) { return ( ); } return ( {currentStep === 1 && ( handleStepChange(2)} /> )} {currentStep === 2 && ( handleStepChange(3)} /> )} {currentStep === 3 && ( handleStepChange(4)} /> )} {currentStep === 4 && } ); }