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 (
-
- {isDark ? (
-
- ) : (
-
- )}
-
- );
-};
-
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 (
-
+
-
-
-
-
-
-
-
-
-
-