ADD:增加路由能力,增加登录页、注册页、项目列表页

This commit is contained in:
yuzhantian 2026-04-23 14:56:01 +08:00
parent 585de35cb4
commit 880bc42985
7 changed files with 621 additions and 2 deletions

58
package-lock.json generated
View File

@ -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",

View File

@ -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": {

View File

@ -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<string[]>([])
const [designFiles, setDesignFiles] = useState<any[]>(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}</> : <Navigate to="/login" />
}
function App() {
return (
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/projects" element={<ProtectedRoute><Projects /></ProtectedRoute>} />
<Route path="/app" element={<ProtectedRoute><MainApp /></ProtectedRoute>} />
<Route path="/" element={<Navigate to="/login" />} />
</Routes>
</Router>
)
}
export default App

View File

@ -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;
}
}

View File

@ -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 (
<div className="auth-container">
<div className="auth-card">
<h2></h2>
{error && <div className="auth-error">{error}</div>}
<form onSubmit={handleLogin} className="auth-form">
<div className="form-group">
<label htmlFor="username"></label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div className="form-group">
<label htmlFor="password"></label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit" className="auth-button" disabled={loading}>
{loading ? '登录中...' : '登录'}
</button>
</form>
<div className="auth-link">
<Link to="/register"></Link>
</div>
</div>
</div>
);
};
export default Login;

View File

@ -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<Project[]>([]);
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 (
<div className="projects-container">
<div className="projects-loading">...</div>
</div>
);
}
return (
<div className="projects-container">
<div className="projects-header">
<h2></h2>
<button className="logout-button" onClick={handleLogout}>
退
</button>
</div>
<div className="projects-list">
{projects.length === 0 ? (
<div className="projects-empty"></div>
) : (
projects.map((project) => (
<div key={project.id} className="project-card">
<h3>{project.name}</h3>
<p>{project.description}</p>
<div className="project-meta">
<span>: {project.createdAt}</span>
<span>: {project.updatedAt}</span>
</div>
</div>
))
)}
</div>
</div>
);
};
export default Projects;

View File

@ -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 (
<div className="auth-container">
<div className="auth-card">
<h2></h2>
{error && <div className="auth-error">{error}</div>}
<form onSubmit={handleRegister} className="auth-form">
<div className="form-group">
<label htmlFor="username"></label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div className="form-group">
<label htmlFor="password"></label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<div className="form-group">
<label htmlFor="confirmPassword"></label>
<input
type="password"
id="confirmPassword"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
/>
</div>
<div className="auth-buttons">
<button type="button" className="auth-button cancel" onClick={handleCancel}>
</button>
<button type="submit" className="auth-button" disabled={loading}>
{loading ? '注册中...' : '注册'}
</button>
</div>
</form>
<div className="auth-link">
<Link to="/login"></Link>
</div>
</div>
</div>
);
};
export default Register;