修改登录主页

This commit is contained in:
huanglinhuan
2025-12-08 10:49:49 +08:00
parent 72bce2dbc8
commit 7c2760c1ec
7 changed files with 366 additions and 49 deletions

View File

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

View File

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

View File

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