修改登录主页
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
import React from 'react';
|
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 { AuthProvider } from './context/AuthContext';
|
||||||
import Login from './pages/Login';
|
import Login from './pages/Login';
|
||||||
import AircraftList from './pages/AircraftList';
|
import AircraftList from './pages/AircraftList';
|
||||||
import AircraftDetail from './pages/AircraftDetail';
|
import AircraftDetail from './pages/AircraftDetail';
|
||||||
import PrivateRoute from './components/PrivateRoute';
|
import PrivateRoute from './components/PrivateRoute';
|
||||||
|
import Navbar from './components/Navbar';
|
||||||
|
import Home from './pages/Home';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -12,6 +14,7 @@ function App() {
|
|||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<Router>
|
<Router>
|
||||||
<div className="App">
|
<div className="App">
|
||||||
|
<Navbar />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route
|
<Route
|
||||||
@@ -30,7 +33,7 @@ function App() {
|
|||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path="/" element={<Navigate to="/aircraft" replace />} />
|
<Route path="/" element={<Home />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</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 { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
@@ -13,18 +13,7 @@ const AircraftDetail = () => {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchAircraftDetail = useCallback(async () => {
|
||||||
fetchAircraftDetail();
|
|
||||||
fetchPdfMaterials();
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (id) {
|
|
||||||
fetchPdfMaterials();
|
|
||||||
}
|
|
||||||
}, [searchTerm, id]);
|
|
||||||
|
|
||||||
const fetchAircraftDetail = async () => {
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await axios.get(`/api/aircraft/${id}`);
|
const response = await axios.get(`/api/aircraft/${id}`);
|
||||||
@@ -40,9 +29,9 @@ const AircraftDetail = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}, [id, logout, navigate]);
|
||||||
|
|
||||||
const fetchPdfMaterials = async () => {
|
const fetchPdfMaterials = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`/api/pdf/aircraft/${id}`, {
|
const response = await axios.get(`/api/pdf/aircraft/${id}`, {
|
||||||
params: { search: searchTerm }
|
params: { search: searchTerm }
|
||||||
@@ -53,7 +42,20 @@ const AircraftDetail = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取PDF资料失败:', error);
|
console.error('获取PDF资料失败:', error);
|
||||||
}
|
}
|
||||||
};
|
}, [id, searchTerm]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchAircraftDetail();
|
||||||
|
fetchPdfMaterials();
|
||||||
|
}, [fetchAircraftDetail, fetchPdfMaterials]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (id) {
|
||||||
|
fetchPdfMaterials();
|
||||||
|
}
|
||||||
|
}, [id, searchTerm, fetchPdfMaterials]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleViewPdf = async (materialId) => {
|
const handleViewPdf = async (materialId) => {
|
||||||
try {
|
try {
|
||||||
@@ -90,17 +92,12 @@ const AircraftDetail = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="aircraft-detail-page">
|
<div className="aircraft-detail-page">
|
||||||
<header className="page-header">
|
<div className="container">
|
||||||
<div className="header-content">
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||||
<button onClick={() => navigate('/aircraft')} className="btn btn-secondary">
|
<button onClick={() => navigate('/aircraft')} className="btn btn-secondary">← 返回列表</button>
|
||||||
← 返回列表
|
<h1 style={{ margin: 0, color: '#333' }}>机型详情</h1>
|
||||||
</button>
|
|
||||||
<h1>机型详情</h1>
|
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className="container">
|
|
||||||
<div className="aircraft-detail-card">
|
<div className="aircraft-detail-card">
|
||||||
<div className="aircraft-header">
|
<div className="aircraft-header">
|
||||||
<div className="aircraft-code-large">{aircraft.code}</div>
|
<div className="aircraft-code-large">{aircraft.code}</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import './AircraftList.css';
|
import './AircraftList.css';
|
||||||
@@ -7,15 +7,13 @@ import './AircraftList.css';
|
|||||||
const AircraftList = () => {
|
const AircraftList = () => {
|
||||||
const [aircrafts, setAircrafts] = useState([]);
|
const [aircrafts, setAircrafts] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const location = useLocation();
|
||||||
const { user, logout } = useAuth();
|
const initialSearch = location.state?.searchTerm || '';
|
||||||
|
const [searchTerm, setSearchTerm] = useState(initialSearch);
|
||||||
|
const { logout } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchAircrafts = useCallback(async () => {
|
||||||
fetchAircrafts();
|
|
||||||
}, [searchTerm]);
|
|
||||||
|
|
||||||
const fetchAircrafts = async () => {
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await axios.get('/api/aircraft/list', {
|
const response = await axios.get('/api/aircraft/list', {
|
||||||
@@ -33,7 +31,11 @@ const AircraftList = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}, [searchTerm, logout, navigate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchAircrafts();
|
||||||
|
}, [fetchAircrafts]);
|
||||||
|
|
||||||
const handleAircraftClick = (id) => {
|
const handleAircraftClick = (id) => {
|
||||||
navigate(`/aircraft/${id}`);
|
navigate(`/aircraft/${id}`);
|
||||||
@@ -41,19 +43,8 @@ const AircraftList = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="aircraft-list-page">
|
<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">
|
<div className="container">
|
||||||
|
<h1 style={{ margin: '0 0 16px', color: '#333' }}>机型信息管理系统</h1>
|
||||||
<div className="search-section">
|
<div className="search-section">
|
||||||
<input
|
<input
|
||||||
type="text"
|
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