import React, { createContext, useState, useContext, useEffect } 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 } from '@radix-ui/themes'; import { toast } from 'hooks/notification'; import { Echoes } from "hooks/echo"; 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 = ({ 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 = () => { return (

恭喜!安装已完成

系统正在重启中,请稍候...

); }; export default function SetupPage() { const [currentStep, setCurrentStep] = useState(() => { return Number(import.meta.env.VITE_INIT_STATUS ?? 0) + 1; }); const [appearance, setAppearance] = useState<'light' | 'dark'>('light'); useEffect(() => { // 在客户端运行时检查主题 const isDark = document.documentElement.classList.contains('dark'); setAppearance(isDark ? 'dark' : 'light'); // 监听主题变化 const handleThemeChange = (event: CustomEvent<{ theme: 'light' | 'dark' }>) => { setAppearance(event.detail.theme); }; window.addEventListener('theme-change', handleThemeChange as EventListener); return () => window.removeEventListener('theme-change', handleThemeChange as EventListener); }, []); return (
{currentStep === 1 && ( setCurrentStep(currentStep + 1)} /> )} {currentStep === 2 && ( setCurrentStep(currentStep + 1)} /> )} {currentStep === 3 && ( setCurrentStep(currentStep + 1)} /> )} {currentStep === 4 && }
); }