修改登录主页
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
import { AuthProvider } from './context/AuthContext';
|
||||
import Login from './pages/Login';
|
||||
import AircraftList from './pages/AircraftList';
|
||||
import AircraftDetail from './pages/AircraftDetail';
|
||||
import PrivateRoute from './components/PrivateRoute';
|
||||
import Navbar from './components/Navbar';
|
||||
import Home from './pages/Home';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
@@ -12,6 +14,7 @@ function App() {
|
||||
<AuthProvider>
|
||||
<Router>
|
||||
<div className="App">
|
||||
<Navbar />
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route
|
||||
@@ -30,7 +33,7 @@ function App() {
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route path="/" element={<Navigate to="/aircraft" replace />} />
|
||||
<Route path="/" element={<Home />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
|
||||
90
src/components/Navbar.css
Executable file
90
src/components/Navbar.css
Executable file
@@ -0,0 +1,90 @@
|
||||
.navbar {
|
||||
background: white;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.navbar-inner {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 12px 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-right: 16px;
|
||||
color: #1890ff;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.navbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
position: relative;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.nav-label {
|
||||
color: #333;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.nav-item:hover .dropdown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 36px;
|
||||
left: 0;
|
||||
background: white;
|
||||
border: 1px solid #e8e8e8;
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
|
||||
border-radius: 6px;
|
||||
min-width: 160px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: block;
|
||||
padding: 8px 12px;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.navbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.user-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.username {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
88
src/components/Navbar.js
Executable file
88
src/components/Navbar.js
Executable file
@@ -0,0 +1,88 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import './Navbar.css';
|
||||
|
||||
const Navbar = () => {
|
||||
const navigate = useNavigate();
|
||||
const { user, logout } = useAuth();
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const handleLoginClick = () => {
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
const handleLogoutClick = () => {
|
||||
logout();
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
const handleSearchKeyDown = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
if (user) {
|
||||
navigate('/aircraft', { state: { searchTerm: search } });
|
||||
} else {
|
||||
navigate('/login');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="navbar">
|
||||
<div className="navbar-inner">
|
||||
<div className="navbar-left">
|
||||
<Link to="/" className="logo">主页</Link>
|
||||
|
||||
<div className="nav-item">
|
||||
<span className="nav-label">产品</span>
|
||||
<div className="dropdown">
|
||||
<Link to="/" className="dropdown-item">产品一</Link>
|
||||
<Link to="/" className="dropdown-item">产品二</Link>
|
||||
<Link to="/" className="dropdown-item">产品三</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="nav-item">
|
||||
<span className="nav-label">解决方案</span>
|
||||
<div className="dropdown">
|
||||
<Link to="/" className="dropdown-item">解决方案A</Link>
|
||||
<Link to="/" className="dropdown-item">解决方案B</Link>
|
||||
<Link to="/" className="dropdown-item">解决方案C</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="nav-item">
|
||||
<span className="nav-label">行业</span>
|
||||
<div className="dropdown">
|
||||
<Link to="/" className="dropdown-item">民航</Link>
|
||||
<Link to="/" className="dropdown-item">通航</Link>
|
||||
<Link to="/" className="dropdown-item">研发制造</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="navbar-right">
|
||||
<input
|
||||
type="text"
|
||||
className="search-input"
|
||||
placeholder="搜索..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onKeyDown={handleSearchKeyDown}
|
||||
/>
|
||||
|
||||
{user ? (
|
||||
<div className="user-area">
|
||||
<span className="username">{user.name || user.username}</span>
|
||||
<button className="btn btn-secondary" onClick={handleLogoutClick}>退出</button>
|
||||
</div>
|
||||
) : (
|
||||
<button className="btn btn-primary" onClick={handleLoginClick}>登录</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import axios from 'axios';
|
||||
@@ -13,18 +13,7 @@ const AircraftDetail = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
fetchAircraftDetail();
|
||||
fetchPdfMaterials();
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
fetchPdfMaterials();
|
||||
}
|
||||
}, [searchTerm, id]);
|
||||
|
||||
const fetchAircraftDetail = async () => {
|
||||
const fetchAircraftDetail = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get(`/api/aircraft/${id}`);
|
||||
@@ -40,9 +29,9 @@ const AircraftDetail = () => {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}, [id, logout, navigate]);
|
||||
|
||||
const fetchPdfMaterials = async () => {
|
||||
const fetchPdfMaterials = useCallback(async () => {
|
||||
try {
|
||||
const response = await axios.get(`/api/pdf/aircraft/${id}`, {
|
||||
params: { search: searchTerm }
|
||||
@@ -53,7 +42,20 @@ const AircraftDetail = () => {
|
||||
} catch (error) {
|
||||
console.error('获取PDF资料失败:', error);
|
||||
}
|
||||
};
|
||||
}, [id, searchTerm]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAircraftDetail();
|
||||
fetchPdfMaterials();
|
||||
}, [fetchAircraftDetail, fetchPdfMaterials]);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
fetchPdfMaterials();
|
||||
}
|
||||
}, [id, searchTerm, fetchPdfMaterials]);
|
||||
|
||||
|
||||
|
||||
const handleViewPdf = async (materialId) => {
|
||||
try {
|
||||
@@ -90,17 +92,12 @@ const AircraftDetail = () => {
|
||||
|
||||
return (
|
||||
<div className="aircraft-detail-page">
|
||||
<header className="page-header">
|
||||
<div className="header-content">
|
||||
<button onClick={() => navigate('/aircraft')} className="btn btn-secondary">
|
||||
← 返回列表
|
||||
</button>
|
||||
<h1>机型详情</h1>
|
||||
<div className="container">
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||
<button onClick={() => navigate('/aircraft')} className="btn btn-secondary">← 返回列表</button>
|
||||
<h1 style={{ margin: 0, color: '#333' }}>机型详情</h1>
|
||||
<div></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="container">
|
||||
<div className="aircraft-detail-card">
|
||||
<div className="aircraft-header">
|
||||
<div className="aircraft-code-large">{aircraft.code}</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import axios from 'axios';
|
||||
import './AircraftList.css';
|
||||
@@ -7,15 +7,13 @@ import './AircraftList.css';
|
||||
const AircraftList = () => {
|
||||
const [aircrafts, setAircrafts] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const { user, logout } = useAuth();
|
||||
const location = useLocation();
|
||||
const initialSearch = location.state?.searchTerm || '';
|
||||
const [searchTerm, setSearchTerm] = useState(initialSearch);
|
||||
const { logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
fetchAircrafts();
|
||||
}, [searchTerm]);
|
||||
|
||||
const fetchAircrafts = async () => {
|
||||
const fetchAircrafts = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get('/api/aircraft/list', {
|
||||
@@ -33,7 +31,11 @@ const AircraftList = () => {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}, [searchTerm, logout, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAircrafts();
|
||||
}, [fetchAircrafts]);
|
||||
|
||||
const handleAircraftClick = (id) => {
|
||||
navigate(`/aircraft/${id}`);
|
||||
@@ -41,19 +43,8 @@ const AircraftList = () => {
|
||||
|
||||
return (
|
||||
<div className="aircraft-list-page">
|
||||
<header className="page-header">
|
||||
<div className="header-content">
|
||||
<h1>机型信息管理系统</h1>
|
||||
<div className="header-actions">
|
||||
<span className="user-info">欢迎,{user?.name || user?.username}</span>
|
||||
<button onClick={logout} className="btn btn-secondary">
|
||||
退出登录
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="container">
|
||||
<h1 style={{ margin: '0 0 16px', color: '#333' }}>机型信息管理系统</h1>
|
||||
<div className="search-section">
|
||||
<input
|
||||
type="text"
|
||||
|
||||
81
src/pages/Home.css
Executable file
81
src/pages/Home.css
Executable file
@@ -0,0 +1,81 @@
|
||||
.home-page {
|
||||
min-height: calc(100vh - 64px);
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 40px 20px 10px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
margin: 0 0 12px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.carousel {
|
||||
position: relative;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.slides {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 100%);
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.slide {
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
min-height: 160px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.slide.active {
|
||||
opacity: 1;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.slide-content h3 { color: #333; margin: 0 0 8px; }
|
||||
.slide-content p { color: #666; }
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: #fff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 50%;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.arrow.left { left: 10px; }
|
||||
.arrow.right { right: 10px; }
|
||||
|
||||
.indicators {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #d9d9d9;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dot.active { background: #1890ff; }
|
||||
|
||||
67
src/pages/Home.js
Executable file
67
src/pages/Home.js
Executable file
@@ -0,0 +1,67 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import './Home.css';
|
||||
|
||||
const slidesData = [
|
||||
{ id: 1, title: '行业资讯:新机型发布', description: '最新机型亮相航空展,性能全面升级', image: '' },
|
||||
{ id: 2, title: '解决方案:资料管理优化', description: '一体化资料管理平台上线,提效30%', image: '' },
|
||||
{ id: 3, title: '产品更新:PDF在线预览', description: '新增在线预览与多端适配功能', image: '' }
|
||||
];
|
||||
|
||||
const Home = () => {
|
||||
const [active, setActive] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setActive((prev) => (prev + 1) % slidesData.length);
|
||||
}, 4000);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
const prevSlide = () => {
|
||||
setActive((prev) => (prev - 1 + slidesData.length) % slidesData.length);
|
||||
};
|
||||
|
||||
const nextSlide = () => {
|
||||
setActive((prev) => (prev + 1) % slidesData.length);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="home-page">
|
||||
<div className="container">
|
||||
<div className="hero">
|
||||
<h1>机型信息平台</h1>
|
||||
<p>欢迎访问主页,无需登录即可浏览导航与新闻</p>
|
||||
</div>
|
||||
|
||||
<div className="carousel">
|
||||
<button className="arrow left" onClick={prevSlide}>‹</button>
|
||||
|
||||
<div className="slides">
|
||||
{slidesData.map((s, idx) => (
|
||||
<div key={s.id} className={`slide ${idx === active ? 'active' : ''}`}>
|
||||
<div className="slide-content">
|
||||
<h3>{s.title}</h3>
|
||||
<p>{s.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button className="arrow right" onClick={nextSlide}>›</button>
|
||||
|
||||
<div className="indicators">
|
||||
{slidesData.map((_, idx) => (
|
||||
<span
|
||||
key={idx}
|
||||
className={`dot ${idx === active ? 'active' : ''}`}
|
||||
onClick={() => setActive(idx)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
Reference in New Issue
Block a user