diff --git a/package-lock.json b/package-lock.json index b3b8242..10c794e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "posthog-node": "^5.29.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^7.14.2", "react-zoom-pan-pinch": "^4.0.3" }, "devDependencies": { @@ -2323,6 +2324,19 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/core-js": { "version": "3.49.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", @@ -3846,6 +3860,44 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.2.tgz", + "integrity": "sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.2.tgz", + "integrity": "sha512-YZcM5ES8jJSM+KrJ9BdvHHqlnGTg5tH3sC5ChFRj4inosKctdyzBDhOyyHdGk597q2OT6NTrCA1OvB/YDwfekQ==", + "license": "MIT", + "dependencies": { + "react-router": "7.14.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/react-zoom-pan-pinch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-4.0.3.tgz", @@ -3989,6 +4041,12 @@ "node": ">=10" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index 5559b95..c6c5e5e 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "posthog-node": "^5.29.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^7.14.2", "react-zoom-pan-pinch": "^4.0.3" }, "devDependencies": { diff --git a/src/App.tsx b/src/App.tsx index 76b7473..f7a293e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,12 @@ -import { useState, useEffect } from 'react' +import { useState } from 'react' +import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom' import CanvasArea from './components/CanvasArea' import TopBar from './components/TopBar' import SideBar from './components/SideBar' import AIChatPanel from './components/AIChatPanel' +import Login from './components/auth/Login' +import Register from './components/auth/Register' +import Projects from './components/auth/Projects' import './App.css' // 模拟设计文件数据 @@ -53,7 +57,8 @@ const MOCK_DESIGN_FILES = [ } ] -function App() { +// 主应用组件 +const MainApp: React.FC = () => { const [isChatVisible, setIsChatVisible] = useState(true) const [selectedFrames, setSelectedFrames] = useState([]) const [designFiles, setDesignFiles] = useState(MOCK_DESIGN_FILES) @@ -102,4 +107,24 @@ function App() { ) } +// 路由保护组件 +const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const isLoggedIn = localStorage.getItem('isLoggedIn') + return isLoggedIn ? <>{children} : +} + +function App() { + return ( + + + } /> + } /> + } /> + } /> + } /> + + + ) +} + export default App diff --git a/src/components/auth/Auth.css b/src/components/auth/Auth.css new file mode 100644 index 0000000..9d5ced8 --- /dev/null +++ b/src/components/auth/Auth.css @@ -0,0 +1,225 @@ +/* 登录和注册页面样式 */ +.auth-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + background-color: #f5f5f5; + padding: 20px; +} + +.auth-card { + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + padding: 30px; + width: 100%; + max-width: 400px; +} + +.auth-card h2 { + margin-top: 0; + margin-bottom: 20px; + color: #333; + text-align: center; +} + +.auth-error { + background-color: #ffebee; + color: #c62828; + padding: 10px; + border-radius: 4px; + margin-bottom: 20px; + font-size: 14px; +} + +.auth-form { + display: flex; + flex-direction: column; + gap: 15px; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 5px; +} + +.form-group label { + font-size: 14px; + color: #555; +} + +.form-group input { + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 16px; +} + +.form-group input:focus { + outline: none; + border-color: #64b5f6; + box-shadow: 0 0 0 2px rgba(100, 181, 246, 0.2); +} + +.auth-button { + background-color: #2196f3; + color: white; + border: none; + padding: 12px; + border-radius: 4px; + font-size: 16px; + cursor: pointer; + transition: background-color 0.3s; +} + +.auth-button:hover { + background-color: #1976d2; +} + +.auth-button:disabled { + background-color: #bdbdbd; + cursor: not-allowed; +} + +.auth-buttons { + display: flex; + gap: 10px; +} + +.auth-buttons .auth-button { + flex: 1; +} + +.auth-button.cancel { + background-color: #9e9e9e; +} + +.auth-button.cancel:hover { + background-color: #757575; +} + +.auth-link { + margin-top: 20px; + text-align: center; + font-size: 14px; + color: #666; +} + +.auth-link a { + color: #2196f3; + text-decoration: none; +} + +.auth-link a:hover { + text-decoration: underline; +} + +/* 项目列表页面样式 */ +.projects-container { + padding: 20px; + max-width: 1200px; + margin: 0 auto; +} + +.projects-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 30px; +} + +.projects-header h2 { + margin: 0; + color: #333; +} + +.logout-button { + background-color: #f44336; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + font-size: 14px; + cursor: pointer; + transition: background-color 0.3s; +} + +.logout-button:hover { + background-color: #d32f2f; +} + +.projects-loading { + text-align: center; + padding: 50px 0; + font-size: 16px; + color: #666; +} + +.projects-empty { + text-align: center; + padding: 50px 0; + font-size: 16px; + color: #666; +} + +.projects-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 20px; +} + +.project-card { + background-color: white; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + padding: 20px; + transition: transform 0.3s, box-shadow 0.3s; +} + +.project-card:hover { + transform: translateY(-5px); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); +} + +.project-card h3 { + margin-top: 0; + margin-bottom: 10px; + color: #333; +} + +.project-card p { + margin-bottom: 15px; + color: #666; + font-size: 14px; +} + +.project-meta { + display: flex; + flex-direction: column; + gap: 5px; + font-size: 12px; + color: #999; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .auth-card { + padding: 20px; + } + + .projects-container { + padding: 10px; + } + + .projects-header { + flex-direction: column; + align-items: flex-start; + gap: 10px; + } + + .projects-list { + grid-template-columns: 1fr; + } +} \ No newline at end of file diff --git a/src/components/auth/Login.tsx b/src/components/auth/Login.tsx new file mode 100644 index 0000000..bbd045a --- /dev/null +++ b/src/components/auth/Login.tsx @@ -0,0 +1,83 @@ +import React, { useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import './Auth.css'; + +const Login: React.FC = () => { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + setLoading(true); + + try { + // 模拟加密密码 + // const encryptedPassword = btoa(password); + + // 预留后台接口调用 + // const response = await fetch('/api/login', { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json', + // }, + // body: JSON.stringify({ username, password: encryptedPassword }), + // }); + + // 模拟登录成功 + setTimeout(() => { + setLoading(false); + // 存储登录状态 + localStorage.setItem('isLoggedIn', 'true'); + localStorage.setItem('username', username); + // 跳转到项目列表页面 + navigate('/projects'); + }, 1000); + } catch (err) { + setLoading(false); + setError('登录失败,请检查用户名和密码'); + } + }; + + return ( +
+
+

登录

+ {error &&
{error}
} +
+
+ + setUsername(e.target.value)} + required + /> +
+
+ + setPassword(e.target.value)} + required + /> +
+ +
+
+ 还没有账号? 立即注册 +
+
+
+ ); +}; + +export default Login; \ No newline at end of file diff --git a/src/components/auth/Projects.tsx b/src/components/auth/Projects.tsx new file mode 100644 index 0000000..004cb54 --- /dev/null +++ b/src/components/auth/Projects.tsx @@ -0,0 +1,117 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import './Auth.css'; + +interface Project { + id: string; + name: string; + description: string; + createdAt: string; + updatedAt: string; +} + +const Projects: React.FC = () => { + const [projects, setProjects] = useState([]); + const [loading, setLoading] = useState(true); + const navigate = useNavigate(); + + // 检查登录状态 + useEffect(() => { + const isLoggedIn = localStorage.getItem('isLoggedIn'); + if (!isLoggedIn) { + navigate('/login'); + } + }, [navigate]); + + // 模拟加载项目数据 + useEffect(() => { + const loadProjects = async () => { + try { + // 预留后台接口调用 + // const response = await fetch('/api/projects'); + // const data = await response.json(); + // setProjects(data); + + // 模拟项目数据 + const mockProjects: Project[] = [ + { + id: '1', + name: 'UI设计项目1', + description: '这是一个UI设计项目', + createdAt: '2026-04-01', + updatedAt: '2026-04-20' + }, + { + id: '2', + name: '网站重构项目', + description: '网站界面重构项目', + createdAt: '2026-04-05', + updatedAt: '2026-04-18' + }, + { + id: '3', + name: '移动应用UI', + description: '移动应用界面设计', + createdAt: '2026-04-10', + updatedAt: '2026-04-22' + } + ]; + + setTimeout(() => { + setProjects(mockProjects); + setLoading(false); + }, 1000); + } catch (error) { + console.error('加载项目失败:', error); + setLoading(false); + } + }; + + loadProjects(); + }, []); + + const handleLogout = () => { + // 清除登录状态 + localStorage.removeItem('isLoggedIn'); + localStorage.removeItem('username'); + // 跳转到登录页面 + navigate('/login'); + }; + + if (loading) { + return ( +
+
加载中...
+
+ ); + } + + return ( +
+
+

项目列表

+ +
+
+ {projects.length === 0 ? ( +
暂无项目
+ ) : ( + projects.map((project) => ( +
+

{project.name}

+

{project.description}

+
+ 创建时间: {project.createdAt} + 更新时间: {project.updatedAt} +
+
+ )) + )} +
+
+ ); +}; + +export default Projects; \ No newline at end of file diff --git a/src/components/auth/Register.tsx b/src/components/auth/Register.tsx new file mode 100644 index 0000000..b94f75f --- /dev/null +++ b/src/components/auth/Register.tsx @@ -0,0 +1,110 @@ +import React, { useState } from 'react'; +import { useNavigate, Link } from 'react-router-dom'; +import './Auth.css'; + +const Register: React.FC = () => { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + + const handleRegister = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + + // 验证密码是否一致 + if (password !== confirmPassword) { + setError('两次输入的密码不一致'); + return; + } + + setLoading(true); + + try { + // 模拟加密密码 + // const encryptedPassword = btoa(password); + + // 预留后台接口调用 + // const response = await fetch('/api/register', { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json', + // }, + // body: JSON.stringify({ username, password: encryptedPassword }), + // }); + + // 模拟注册成功 + setTimeout(() => { + setLoading(false); + // 存储登录状态 + localStorage.setItem('isLoggedIn', 'true'); + localStorage.setItem('username', username); + // 跳转到项目列表页面 + navigate('/projects'); + }, 1000); + } catch (err) { + setLoading(false); + setError('注册失败,请稍后再试'); + } + }; + + const handleCancel = () => { + navigate('/login'); + }; + + return ( +
+
+

注册

+ {error &&
{error}
} +
+
+ + setUsername(e.target.value)} + required + /> +
+
+ + setPassword(e.target.value)} + required + /> +
+
+ + setConfirmPassword(e.target.value)} + required + /> +
+
+ + +
+
+
+ 已有账号? 立即登录 +
+
+
+ ); +}; + +export default Register; \ No newline at end of file