diff --git a/frontend/app/env.ts b/frontend/app/env.ts index d747d6f..32a086d 100644 --- a/frontend/app/env.ts +++ b/frontend/app/env.ts @@ -5,6 +5,7 @@ export interface EnvConfig { VITE_API_BASE_URL: string; VITE_API_USERNAME: string; VITE_API_PASSWORD: string; + VITE_PATTERN: string; } export const DEFAULT_CONFIG: EnvConfig = { @@ -14,11 +15,12 @@ export const DEFAULT_CONFIG: EnvConfig = { VITE_API_BASE_URL: "http://127.0.0.1:22000", VITE_API_USERNAME: "", VITE_API_PASSWORD: "", + VITE_PATTERN: "true" } as const; // 扩展 ImportMeta 接口 declare global { - interface ImportMetaEnv extends EnvConfig {} + interface ImportMetaEnv extends EnvConfig { } interface ImportMeta { readonly env: ImportMetaEnv; } diff --git a/frontend/app/index.css b/frontend/app/index.css index bd6213e..416aec6 100644 --- a/frontend/app/index.css +++ b/frontend/app/index.css @@ -1,3 +1,117 @@ +@import "@radix-ui/themes/styles.css"; @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +:root { + --transition-duration: 150ms; + --transition-easing: cubic-bezier(0.4, 0, 0.2, 1); + --logo-path-length: 1000; +} + +/* 基础过渡效果 */ +.radix-themes { + transition: + background-color var(--transition-duration) var(--transition-easing), + color var(--transition-duration) var(--transition-easing); +} + +/* 主题过渡效果 */ +.dark body, +body { + transition: background-color var(--transition-duration) var(--transition-easing); +} + +/* 基础布局样式 */ +html, +body { + height: 100%; +} + +/* 响应式调整 */ +@media (max-width: 640px) { + html { + font-size: 14px; + } +} + +/* Logo 动画 */ +@keyframes logo-anim { + 0% { + stroke-dashoffset: var(--logo-path-length); + stroke-dasharray: var(--logo-path-length) var(--logo-path-length); + opacity: 0; + fill: transparent; + } + + 5% { + opacity: 1; + stroke-dashoffset: var(--logo-path-length); + stroke-dasharray: var(--logo-path-length) var(--logo-path-length); + } + + /* 慢速绘画过程 */ + 45% { + stroke-dashoffset: 0; + stroke-dasharray: var(--logo-path-length) var(--logo-path-length); + fill: transparent; + } + + /* 慢慢填充效果 */ + 50% { + stroke-dashoffset: 0; + stroke-dasharray: var(--logo-path-length) var(--logo-path-length); + fill: currentColor; + opacity: 1; + } + + /* 保持填充状态 */ + 75% { + stroke-dashoffset: 0; + stroke-dasharray: var(--logo-path-length) var(--logo-path-length); + fill: currentColor; + opacity: 1; + } + + /* 变回线条 */ + 85% { + stroke-dashoffset: 0; + stroke-dasharray: var(--logo-path-length) var(--logo-path-length); + fill: transparent; + opacity: 1; + } + + /* 线条消失 */ + 95% { + stroke-dashoffset: var(--logo-path-length); + stroke-dasharray: var(--logo-path-length) var(--logo-path-length); + fill: transparent; + opacity: 1; + } + + 100% { + stroke-dashoffset: var(--logo-path-length); + stroke-dasharray: var(--logo-path-length) var(--logo-path-length); + fill: transparent; + opacity: 0; + } +} + +#logo-anim, +#logo-anim path { + fill: transparent; + stroke: currentColor; + stroke-width: 2; + stroke-dashoffset: var(--logo-path-length); + stroke-dasharray: var(--logo-path-length) var(--logo-path-length); + animation: logo-anim 15s cubic-bezier(0.4, 0, 0.2, 1) infinite; + transform-origin: center; + stroke-linecap: round; + stroke-linejoin: round; +} + +/* 确保 Logo 在暗色模式下的颜色正确 */ +.dark #logo-anim, +.dark #logo-anim path { + stroke: currentColor; +} diff --git a/frontend/app/init.tsx b/frontend/app/init.tsx index afed4bd..04957f1 100644 --- a/frontend/app/init.tsx +++ b/frontend/app/init.tsx @@ -1,8 +1,10 @@ -import React, { createContext, useState, useEffect } from "react"; -import { message } from "hooks/message"; +import React, { createContext, useState, useContext, useEffect } from "react"; import {DEFAULT_CONFIG} from "app/env" -import { useHub } from "core/hub"; - +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; @@ -23,14 +25,12 @@ const StepContainer: React.FC<{ title: string; children: React.ReactNode }> = ({ title, children, }) => ( -
-

- {title} -

-
+ + {title} + {children} -
-
+ + ); // 通用的导航按钮组件 @@ -39,45 +39,37 @@ const NavigationButtons: React.FC ( -
- -
+ + ); -// 输入框组件 +// 修改输入框组件 const InputField: React.FC<{ label: string; name: string; defaultValue?: string | number; hint?: string; required?: boolean; -}> = ({ label, name, defaultValue, hint, required = true }) => ( -
-

- {label} {required && *} -

+}> = ({ label, name, defaultValue, hint, required = true }) => ( + + + {label} {required && *} + - {hint && ( -

- {hint} -

- )} -
+ {hint && {hint}} + ); const Introduction: React.FC = ({ onNext }) => ( @@ -94,7 +86,7 @@ const Introduction: React.FC = ({ onNext }) => ( const DatabaseConfig: React.FC = ({ onNext }) => { const [dbType, setDbType] = useState("postgresql"); const [loading, setLoading] = useState(false); - const http = useHub().http; + const http = HttpClient.getInstance(); const validateForm = () => { const getRequiredFields = () => { @@ -131,14 +123,15 @@ const DatabaseConfig: React.FC = ({ onNext }) => { default: return field; } }); - message.error(`请填写以下必填项:${fieldNames.join('、')}`); + toast.error(`请填写以下必填项:${fieldNames.join('、')}`); return false; } return true; }; const handleNext = async () => { - if (!validateForm()) { + const validation = validateForm(); + if (validation !== true) { return; } @@ -176,11 +169,12 @@ const DatabaseConfig: React.FC = ({ onNext }) => { Object.assign(import.meta.env, newEnv); - message.success('数据库配置成功!'); + toast.success('数据库配置成功!'); + setTimeout(() => onNext(), 1000); } catch (error: any) { - console.error( error); - message.error(error.message,error.title); + console.error(error); + toast.error(error.message, error.title); } finally { setLoading(false); } @@ -189,20 +183,21 @@ const DatabaseConfig: React.FC = ({ onNext }) => { return (
-
-

+ + 数据库类型 -

- -
+ + + + + + PostgreSQL + MySQL + SQLite + + + + {dbType === "postgresql" && ( <> @@ -327,7 +322,7 @@ interface InstallReplyData { const AdminConfig: React.FC = ({ onNext }) => { const [loading, setLoading] = useState(false); - const http = useHub().http; + const http = HttpClient.getInstance(); const handleNext = async () => { setLoading(true); @@ -365,11 +360,11 @@ const AdminConfig: React.FC = ({ onNext }) => { Object.assign(import.meta.env, newEnv); - message.success('管理员账号创建成功!'); + toast.success('管理员账号创建成功!'); onNext(); } catch (error: any) { console.error(error); - message.error(error.message,error.title); + toast.error(error.message, error.title); } finally { setLoading(false); } @@ -388,9 +383,6 @@ const AdminConfig: React.FC = ({ onNext }) => { }; const SetupComplete: React.FC = () => { - const http = useHub().http; - - return ( @@ -409,76 +401,64 @@ const SetupComplete: React.FC = () => { ); }; -// 修改主题切换按钮组件 -const ThemeToggle: React.FC = () => { - const [isDark, setIsDark] = useState(false); - const [isVisible, setIsVisible] = useState(true); - - useEffect(() => { - const isDarkMode = document.documentElement.classList.contains('dark'); - setIsDark(isDarkMode); - - const handleScroll = () => { - const currentScrollPos = window.scrollY; - setIsVisible(currentScrollPos < 100); // 滚动超过100px就隐藏 - }; - - window.addEventListener('scroll', handleScroll); - return () => window.removeEventListener('scroll', handleScroll); - }, []); - - const toggleTheme = () => { - const newIsDark = !isDark; - setIsDark(newIsDark); - document.documentElement.classList.toggle('dark'); - }; - - 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 && } - + +
+
+ +
+
+
+ +
+
+ + + + {currentStep === 1 && ( + setCurrentStep(currentStep + 1)} /> + )} + {currentStep === 2 && ( + setCurrentStep(currentStep + 1)} + /> + )} + {currentStep === 3 && ( + setCurrentStep(currentStep + 1)} + /> + )} + {currentStep === 4 && } + + +
-
+ ); } \ No newline at end of file diff --git a/frontend/app/root.tsx b/frontend/app/root.tsx index 7e5ab34..9c7b973 100644 --- a/frontend/app/root.tsx +++ b/frontend/app/root.tsx @@ -5,15 +5,35 @@ import { Scripts, ScrollRestoration, } from "@remix-run/react"; - -import { HubProvider } from "core/hub"; -import { MessageProvider, MessageContainer } from "hooks/message"; +import { NotificationProvider } from "hooks/notification"; +import { Theme } from '@radix-ui/themes'; +import { useEffect, useState } from "react"; import "~/index.css"; -export function Layout({ children }: { children: React.ReactNode }) { +export function Layout() { + const [theme, setTheme] = useState<'light' | 'dark'>('light'); + + useEffect(() => { + // 初始化主题 + const isDark = document.documentElement.classList.contains('dark'); + setTheme(isDark ? 'dark' : 'light'); + + // 监听主题变化 + const handleThemeChange = (event: CustomEvent<{ theme: 'light' | 'dark' }>) => { + setTheme(event.detail.theme); + }; + + window.addEventListener('theme-change', handleThemeChange as EventListener); + return () => window.removeEventListener('theme-change', handleThemeChange as EventListener); + }, []); + return ( - + - - - - - - - - - -