ADD:增加路由能力,增加登录页、注册页、项目列表页
This commit is contained in:
parent
585de35cb4
commit
880bc42985
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
29
src/App.tsx
29
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<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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
Loading…
Reference in New Issue