diff --git a/frontend/README.md b/frontend/README.md
new file mode 100644
index 0000000..80fe84f
--- /dev/null
+++ b/frontend/README.md
@@ -0,0 +1,87 @@
+# AnyNAT Frontend
+
+这是AnyNAT项目的React前端应用,提供用户登录注册和管理界面。
+
+## 功能特性
+
+- 🔐 用户登录/注册
+- 📱 响应式设计
+- 🎨 现代化UI界面
+- ⚡ TypeScript支持
+- 🛡️ 表单验证
+
+## 快速开始
+
+### 安装依赖
+
+```bash
+cd frontend
+npm install
+```
+
+### 启动开发服务器
+
+```bash
+npm start
+```
+
+应用将在 [http://localhost:3000](http://localhost:3000) 启动。
+
+### 构建生产版本
+
+```bash
+npm run build
+```
+
+## 演示账户
+
+为了方便测试,应用提供了演示账户:
+
+- **邮箱**: admin@anynat.com
+- **密码**: password
+
+## 项目结构
+
+```
+frontend/
+├── public/
+│ └── index.html # HTML模板
+├── src/
+│ ├── components/ # 可复用组件
+│ │ └── AuthForm.tsx # 认证表单组件
+│ ├── pages/ # 页面组件
+│ │ ├── LoginPage.tsx # 登录页面
+│ │ ├── RegisterPage.tsx # 注册页面
+│ │ └── DashboardPage.tsx # 仪表板页面
+│ ├── styles/ # 样式文件
+│ │ ├── App.css # 全局样式
+│ │ ├── AuthPage.css # 认证页面样式
+│ │ └── Dashboard.css # 仪表板样式
+│ ├── App.tsx # 主应用组件
+│ └── index.tsx # 应用入口
+├── package.json # 项目配置
+└── tsconfig.json # TypeScript配置
+```
+
+## 技术栈
+
+- **React 18** - 用户界面库
+- **TypeScript** - 类型安全
+- **React Router** - 路由管理
+- **CSS3** - 样式和动画
+
+## 开发说明
+
+1. 登录和注册功能目前使用模拟数据
+2. 需要与后端API集成以实现真实的用户认证
+3. 可以根据需要添加更多功能页面
+
+## API集成
+
+要集成真实的后端API,请修改以下文件中的API调用:
+
+- `src/pages/LoginPage.tsx` - 登录API
+- `src/pages/RegisterPage.tsx` - 注册API
+
+将模拟的API调用替换为实际的fetch请求。
+
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..e74216f
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "anynat-frontend",
+ "version": "1.0.0",
+ "private": true,
+ "dependencies": {
+ "@types/node": "^16.18.0",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^6.8.0",
+ "react-scripts": "5.0.1",
+ "typescript": "^4.9.0",
+ "web-vitals": "^2.1.0"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "devDependencies": {
+ "@types/jest": "^27.5.0"
+ }
+}
+
diff --git a/frontend/public/index.html b/frontend/public/index.html
new file mode 100644
index 0000000..01cb97d
--- /dev/null
+++ b/frontend/public/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+ AnyNAT - 登录
+
+
+
+
+
+
+
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
new file mode 100644
index 0000000..eee5361
--- /dev/null
+++ b/frontend/src/App.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
+import LoginPage from './pages/LoginPage';
+import RegisterPage from './pages/RegisterPage';
+import DashboardPage from './pages/DashboardPage';
+import './styles/App.css';
+
+function App() {
+ return (
+
+
+
+ } />
+ } />
+ } />
+ } />
+
+
+
+ );
+}
+
+export default App;
+
diff --git a/frontend/src/components/AuthForm.tsx b/frontend/src/components/AuthForm.tsx
new file mode 100644
index 0000000..6bed07b
--- /dev/null
+++ b/frontend/src/components/AuthForm.tsx
@@ -0,0 +1,83 @@
+import React, { useState } from 'react';
+
+interface FormField {
+ name: string;
+ type: string;
+ placeholder: string;
+ label: string;
+ required?: boolean;
+}
+
+interface AuthFormProps {
+ title: string;
+ fields: FormField[];
+ onSubmit: (formData: any) => void;
+ loading: boolean;
+ error: string;
+ submitText: string;
+}
+
+const AuthForm: React.FC = ({
+ title,
+ fields,
+ onSubmit,
+ loading,
+ error,
+ submitText,
+}) => {
+ const [formData, setFormData] = useState>({});
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target;
+ setFormData(prev => ({
+ ...prev,
+ [name]: value,
+ }));
+ };
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ onSubmit(formData);
+ };
+
+ return (
+
+
{title}
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+ );
+};
+
+export default AuthForm;
+
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
new file mode 100644
index 0000000..5197965
--- /dev/null
+++ b/frontend/src/index.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+
+const root = ReactDOM.createRoot(
+ document.getElementById('root') as HTMLElement
+);
+
+root.render(
+
+
+
+);
+
diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx
new file mode 100644
index 0000000..765b83e
--- /dev/null
+++ b/frontend/src/pages/DashboardPage.tsx
@@ -0,0 +1,88 @@
+import React, { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import '../styles/Dashboard.css';
+
+const DashboardPage: React.FC = () => {
+ const [user, setUser] = useState<{ email: string } | null>(null);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ // 检查用户是否已登录
+ const token = localStorage.getItem('authToken');
+ if (!token) {
+ navigate('/login');
+ return;
+ }
+
+ // 模拟获取用户信息
+ setUser({ email: 'admin@anynat.com' });
+ }, [navigate]);
+
+ const handleLogout = () => {
+ localStorage.removeItem('authToken');
+ navigate('/login');
+ };
+
+ if (!user) {
+ return 加载中...
;
+ }
+
+ return (
+
+
+
+
+
+
+
欢迎使用 AnyNAT
+
您已成功登录到高性能内网穿透工具控制台
+
+
+
+
+
🚀 高性能
+
基于Node.js和MQTT协议,提供高效的内网穿透服务
+
+
+
+
🔒 安全可靠
+
采用加密传输,确保数据安全
+
+
+
+
⚡ 快速部署
+
支持Docker容器化部署,快速启动
+
+
+
+
📊 实时监控
+
提供实时连接状态和流量监控
+
+
+
+
+
快速操作
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default DashboardPage;
+
diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx
new file mode 100644
index 0000000..08e0e92
--- /dev/null
+++ b/frontend/src/pages/LoginPage.tsx
@@ -0,0 +1,102 @@
+import React, { useState } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import AuthForm from '../components/AuthForm';
+import '../styles/AuthPage.css';
+
+interface LoginFormData {
+ email: string;
+ password: string;
+}
+
+const LoginPage: React.FC = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+ const navigate = useNavigate();
+
+ const handleLogin = async (formData: LoginFormData) => {
+ setLoading(true);
+ setError('');
+
+ try {
+ // 这里应该调用实际的登录API
+ // const response = await fetch('/api/login', {
+ // method: 'POST',
+ // headers: {
+ // 'Content-Type': 'application/json',
+ // },
+ // body: JSON.stringify(formData),
+ // });
+
+ // 模拟API调用
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // 模拟登录验证
+ if (formData.email === 'admin@anynat.com' && formData.password === 'password') {
+ // 登录成功,保存token到localStorage
+ localStorage.setItem('authToken', 'mock-jwt-token');
+ navigate('/dashboard');
+ } else {
+ setError('邮箱或密码错误');
+ }
+ } catch (err) {
+ setError('登录失败,请稍后重试');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const loginFields = [
+ {
+ name: 'email',
+ type: 'email',
+ placeholder: '请输入邮箱',
+ label: '邮箱',
+ required: true,
+ },
+ {
+ name: 'password',
+ type: 'password',
+ placeholder: '请输入密码',
+ label: '密码',
+ required: true,
+ },
+ ];
+
+ return (
+
+
+
+
+
+
+
+
+ 还没有账户?
+
+ 立即注册
+
+
+
+
+
+
演示账户:
+
邮箱: admin@anynat.com
+
密码: password
+
+
+
+ );
+};
+
+export default LoginPage;
+
diff --git a/frontend/src/pages/RegisterPage.tsx b/frontend/src/pages/RegisterPage.tsx
new file mode 100644
index 0000000..2ca922e
--- /dev/null
+++ b/frontend/src/pages/RegisterPage.tsx
@@ -0,0 +1,125 @@
+import React, { useState } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import AuthForm from '../components/AuthForm';
+import '../styles/AuthPage.css';
+
+interface RegisterFormData {
+ username: string;
+ email: string;
+ password: string;
+ confirmPassword: string;
+}
+
+const RegisterPage: React.FC = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+ const navigate = useNavigate();
+
+ const handleRegister = async (formData: RegisterFormData) => {
+ setLoading(true);
+ setError('');
+
+ // 验证密码确认
+ if (formData.password !== formData.confirmPassword) {
+ setError('两次输入的密码不一致');
+ setLoading(false);
+ return;
+ }
+
+ // 验证密码强度
+ if (formData.password.length < 6) {
+ setError('密码长度至少为6位');
+ setLoading(false);
+ return;
+ }
+
+ try {
+ // 这里应该调用实际的注册API
+ // const response = await fetch('/api/register', {
+ // method: 'POST',
+ // headers: {
+ // 'Content-Type': 'application/json',
+ // },
+ // body: JSON.stringify({
+ // username: formData.username,
+ // email: formData.email,
+ // password: formData.password,
+ // }),
+ // });
+
+ // 模拟API调用
+ await new Promise(resolve => setTimeout(resolve, 1500));
+
+ // 模拟注册成功
+ alert('注册成功!请登录您的账户。');
+ navigate('/login');
+ } catch (err) {
+ setError('注册失败,请稍后重试');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const registerFields = [
+ {
+ name: 'username',
+ type: 'text',
+ placeholder: '请输入用户名',
+ label: '用户名',
+ required: true,
+ },
+ {
+ name: 'email',
+ type: 'email',
+ placeholder: '请输入邮箱',
+ label: '邮箱',
+ required: true,
+ },
+ {
+ name: 'password',
+ type: 'password',
+ placeholder: '请输入密码(至少6位)',
+ label: '密码',
+ required: true,
+ },
+ {
+ name: 'confirmPassword',
+ type: 'password',
+ placeholder: '请再次输入密码',
+ label: '确认密码',
+ required: true,
+ },
+ ];
+
+ return (
+
+ );
+};
+
+export default RegisterPage;
+
diff --git a/frontend/src/styles/App.css b/frontend/src/styles/App.css
new file mode 100644
index 0000000..77016ac
--- /dev/null
+++ b/frontend/src/styles/App.css
@@ -0,0 +1,29 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ min-height: 100vh;
+}
+
+.App {
+ min-height: 100vh;
+}
+
+.loading {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ font-size: 18px;
+ color: #666;
+}
+
diff --git a/frontend/src/styles/AuthPage.css b/frontend/src/styles/AuthPage.css
new file mode 100644
index 0000000..d09bb26
--- /dev/null
+++ b/frontend/src/styles/AuthPage.css
@@ -0,0 +1,181 @@
+.auth-page {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+}
+
+.auth-container {
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
+ padding: 40px;
+ width: 100%;
+ max-width: 400px;
+ animation: slideUp 0.5s ease-out;
+}
+
+@keyframes slideUp {
+ from {
+ opacity: 0;
+ transform: translateY(30px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.auth-header {
+ text-align: center;
+ margin-bottom: 30px;
+}
+
+.auth-header h1 {
+ color: #333;
+ font-size: 32px;
+ font-weight: 700;
+ margin-bottom: 8px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.auth-header p {
+ color: #666;
+ font-size: 14px;
+}
+
+.auth-form h2 {
+ color: #333;
+ font-size: 24px;
+ font-weight: 600;
+ margin-bottom: 20px;
+ text-align: center;
+}
+
+.form-group {
+ margin-bottom: 20px;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 6px;
+ color: #333;
+ font-weight: 500;
+ font-size: 14px;
+}
+
+.form-group input {
+ width: 100%;
+ padding: 12px 16px;
+ border: 2px solid #e1e5e9;
+ border-radius: 8px;
+ font-size: 16px;
+ transition: all 0.3s ease;
+ background: #f8f9fa;
+}
+
+.form-group input:focus {
+ outline: none;
+ border-color: #667eea;
+ background: white;
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
+}
+
+.form-group input:disabled {
+ background: #f5f5f5;
+ cursor: not-allowed;
+}
+
+.submit-btn {
+ width: 100%;
+ padding: 14px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ border: none;
+ border-radius: 8px;
+ font-size: 16px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ margin-top: 10px;
+}
+
+.submit-btn:hover:not(:disabled) {
+ transform: translateY(-2px);
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
+}
+
+.submit-btn:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+ transform: none;
+}
+
+.error-message {
+ background: #fee;
+ color: #c33;
+ padding: 12px;
+ border-radius: 6px;
+ margin-bottom: 20px;
+ border: 1px solid #fcc;
+ font-size: 14px;
+ text-align: center;
+}
+
+.auth-footer {
+ text-align: center;
+ margin-top: 30px;
+ padding-top: 20px;
+ border-top: 1px solid #eee;
+}
+
+.auth-footer p {
+ color: #666;
+ font-size: 14px;
+}
+
+.auth-link {
+ color: #667eea;
+ text-decoration: none;
+ font-weight: 600;
+ margin-left: 5px;
+}
+
+.auth-link:hover {
+ text-decoration: underline;
+}
+
+.demo-info {
+ margin-top: 20px;
+ padding: 15px;
+ background: #f8f9fa;
+ border-radius: 8px;
+ border-left: 4px solid #667eea;
+}
+
+.demo-info p {
+ font-size: 12px;
+ color: #666;
+ margin-bottom: 4px;
+}
+
+.demo-info p:first-child {
+ font-weight: 600;
+ color: #333;
+}
+
+@media (max-width: 480px) {
+ .auth-container {
+ padding: 30px 20px;
+ margin: 10px;
+ }
+
+ .auth-header h1 {
+ font-size: 28px;
+ }
+}
+
diff --git a/frontend/src/styles/Dashboard.css b/frontend/src/styles/Dashboard.css
new file mode 100644
index 0000000..1a886bb
--- /dev/null
+++ b/frontend/src/styles/Dashboard.css
@@ -0,0 +1,199 @@
+.dashboard {
+ min-height: 100vh;
+ background: #f5f7fa;
+}
+
+.dashboard-header {
+ background: white;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ position: sticky;
+ top: 0;
+ z-index: 100;
+}
+
+.header-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.dashboard-header h1 {
+ color: #333;
+ font-size: 28px;
+ font-weight: 700;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.user-info {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+}
+
+.user-info span {
+ color: #666;
+ font-weight: 500;
+}
+
+.logout-btn {
+ padding: 8px 16px;
+ background: #dc3545;
+ color: white;
+ border: none;
+ border-radius: 6px;
+ font-size: 14px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.logout-btn:hover {
+ background: #c82333;
+ transform: translateY(-1px);
+}
+
+.dashboard-main {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 40px 20px;
+}
+
+.welcome-section {
+ text-align: center;
+ margin-bottom: 50px;
+}
+
+.welcome-section h2 {
+ color: #333;
+ font-size: 36px;
+ font-weight: 700;
+ margin-bottom: 10px;
+}
+
+.welcome-section p {
+ color: #666;
+ font-size: 18px;
+}
+
+.features-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ gap: 30px;
+ margin-bottom: 50px;
+}
+
+.feature-card {
+ background: white;
+ padding: 30px;
+ border-radius: 12px;
+ box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
+ transition: all 0.3s ease;
+ text-align: center;
+}
+
+.feature-card:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
+}
+
+.feature-card h3 {
+ color: #333;
+ font-size: 20px;
+ font-weight: 600;
+ margin-bottom: 15px;
+}
+
+.feature-card p {
+ color: #666;
+ line-height: 1.6;
+}
+
+.actions-section {
+ background: white;
+ padding: 40px;
+ border-radius: 12px;
+ box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
+}
+
+.actions-section h3 {
+ color: #333;
+ font-size: 24px;
+ font-weight: 600;
+ margin-bottom: 25px;
+ text-align: center;
+}
+
+.action-buttons {
+ display: flex;
+ gap: 15px;
+ justify-content: center;
+ flex-wrap: wrap;
+}
+
+.action-btn {
+ padding: 12px 24px;
+ border: 2px solid #e1e5e9;
+ border-radius: 8px;
+ font-size: 16px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ background: white;
+ color: #333;
+}
+
+.action-btn.primary {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ border-color: transparent;
+}
+
+.action-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
+}
+
+.action-btn.primary:hover {
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
+}
+
+@media (max-width: 768px) {
+ .header-content {
+ flex-direction: column;
+ gap: 15px;
+ text-align: center;
+ }
+
+ .dashboard-header h1 {
+ font-size: 24px;
+ }
+
+ .welcome-section h2 {
+ font-size: 28px;
+ }
+
+ .features-grid {
+ grid-template-columns: 1fr;
+ gap: 20px;
+ }
+
+ .actions-section {
+ padding: 30px 20px;
+ }
+
+ .action-buttons {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .action-btn {
+ width: 100%;
+ max-width: 200px;
+ }
+}
+
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 0000000..1b8e6eb
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "es6"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": [
+ "src"
+ ]
+}
+