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"; 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 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 formData = { db_type: dbType, host: ( document.querySelector('[name="db_host"]') as HTMLInputElement )?.value?.trim() ?? "", db_prefix: ( document.querySelector('[name="db_prefix"]') as HTMLInputElement )?.value?.trim() ?? "", port: Number( ( document.querySelector('[name="db_port"]') as HTMLInputElement )?.value?.trim() ?? 0, ), user: ( document.querySelector('[name="db_user"]') as HTMLInputElement )?.value?.trim() ?? "", password: ( document.querySelector('[name="db_password"]') as HTMLInputElement )?.value?.trim() ?? "", db_name: ( document.querySelector('[name="db_name"]') as HTMLInputElement )?.value?.trim() ?? "", }; await http.post("/sql", formData); 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, VITE_INIT_STATUS: "2", }; await http.dev("/env", { method: "POST", body: JSON.stringify(newEnv), }); Object.assign(import.meta.env, newEnv); toast.success("数据库配置成功!"); setTimeout(() => onNext(), 1000); } catch (error: any) { console.error(error); toast.error(error.message, error.title); } 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 = { username: ( document.querySelector('[name="admin_username"]') as HTMLInputElement )?.value, password: ( document.querySelector('[name="admin_password"]') as HTMLInputElement )?.value, email: ( document.querySelector('[name="admin_email"]') as HTMLInputElement )?.value, }; const response = (await http.post( "/administrator", formData, )) as InstallReplyData; const data = response; localStorage.setItem("token", data.token); 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, VITE_INIT_STATUS: "3", VITE_API_USERNAME: data.username, VITE_API_PASSWORD: data.password, }; await http.dev("/env", { method: "POST", body: JSON.stringify(newEnv), }); Object.assign(import.meta.env, newEnv); toast.success("管理员账号创建成功!"); onNext(); } catch (error: any) { console.error(error); toast.error(error.message, error.title); } finally { setLoading(false); } }; return (
); }; const SetupComplete: React.FC = () => ( 恭喜!安装已完成 系统正在重启中,请稍候... ); export default function SetupPage() { const [currentStep, setCurrentStep] = useState(1); useEffect(() => { // 在客户端组件挂载后更新状态 const initStatus = Number(import.meta.env.VITE_INIT_STATUS ?? 0) + 1; setCurrentStep(initStatus); }, []); return ( {currentStep === 1 && ( setCurrentStep(currentStep + 1)} /> )} {currentStep === 2 && ( setCurrentStep(currentStep + 1)} /> )} {currentStep === 3 && ( setCurrentStep(currentStep + 1)} /> )} {currentStep === 4 && } ); }