diff --git a/src/App.js b/src/App.js index 8afe742..88096ef 100755 --- a/src/App.js +++ b/src/App.js @@ -2,10 +2,7 @@ import React from 'react'; 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 ProductDetail from './pages/ProductDetail'; -import PrivateRoute from './components/PrivateRoute'; +import ProductDetail from './pages/ProductDetail'; import Navbar from './components/Navbar'; import Home from './pages/Home'; import './App.css'; @@ -14,30 +11,13 @@ function App() { return ( -
} /> - - - - } - /> - - - - } - /> - } /> - } /> + + } /> + } /> -
); diff --git a/src/components/Navbar.css b/src/components/Navbar.css index 6f70353..1e8e3a2 100755 --- a/src/components/Navbar.css +++ b/src/components/Navbar.css @@ -45,16 +45,15 @@ .nav-label { color: #333; - cursor: default; + cursor: pointer; + font-weight: 700; + display: inline-block; + position: relative; } -.nav-item:hover .dropdown { - display: block; -} +.nav-item:hover .dropdown, .nav-item:hover .dropdown.mega { - display: flex; - flex-wrap: nowrap; - align-items: flex-start; + display: none; } .nav-item.open .dropdown { display: block; @@ -130,13 +129,23 @@ background: #fff; pointer-events: auto; } -.nav-item.open .dropdown.mega, -.nav-item:hover .dropdown.mega { +.nav-item.open .dropdown.mega { display: flex; flex-wrap: nowrap; align-items: flex-start; } +.nav-item.selected .nav-label::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: -2px; + height: 2px; + background: #1890ff; + border-radius: 2px; +} + .mega-col { width: 320px; padding: 0 16px; diff --git a/src/components/Navbar.js b/src/components/Navbar.js index b04d3b1..bcfef4b 100755 --- a/src/components/Navbar.js +++ b/src/components/Navbar.js @@ -12,70 +12,70 @@ const productMenu = [ key: 'N96', label: 'N96', products: [ - { name: 'N96', desc: 'Android11,高性能,适配复杂场景', image: 'http://localhost:3001/uploads/menu/menu_n96.png ' } + { name: 'N96', desc: 'Android11,高性能,适配复杂场景', image: '/uploads/menu/menu_n96.png' } ] }, { key: 'N96P', label: 'N96P', products: [ - { name: 'N96P', desc: 'Android14,高性能,适配复杂场景', image: 'http://localhost:3001/uploads/menu/menu_n96p.png' } + { name: 'N96P', desc: 'Android14,高性能,适配复杂场景', image: '/uploads/menu/menu_n96p.png' } ] }, { key: 'N92', label: 'N92', products: [ - { name: 'N92', desc: 'Android14,高性能,适配复杂场景', image: 'http://localhost:3001/uploads/menu/menu_n92.png' } + { name: 'N92', desc: 'Android13,高性能,适配复杂场景', image: '/uploads/menu/menu_n92.png' } ] }, { key: 'N86P', label: 'N86P', products: [ - { name: 'N86P', desc: 'Android14,高性能,适配复杂场景', image: 'http://localhost:3001/uploads/menu/menu_n86p.png' } + { name: 'N86P', desc: 'Android13,高性能,适配复杂场景', image: '/uploads/menu/menu_n86p.png' } ] }, { key: 'N86', label: 'N86', products: [ - { name: 'N86', desc: 'Android14,高性能,适配复杂场景', image: 'http://localhost:3001/uploads/menu/menu_n86.png' } + { name: 'N86', desc: 'Android9,高性能,适配复杂场景', image: '/uploads/menu/menu_n86.png' } ] }, { key: 'N82', label: 'N82', products: [ - { name: 'N82', desc: 'Android14,高性能,适配复杂场景', image: 'http://localhost:3001/uploads/menu/menu_n82.png' } + { name: 'N82', desc: 'Android10,高性能,适配复杂场景', image: '/uploads/menu/menu_n82.png' } ] }, { key: 'N80', label: 'N80', products: [ - { name: 'N80', desc: 'Android14,高性能,适配复杂场景', image: 'http://localhost:3001/uploads/menu/menu_n80.png' } + { name: 'N80', desc: 'Android13,高性能,适配复杂场景', image: '/uploads/menu/menu_n80.png' } ] }, { key: 'N62', label: 'N62', products: [ - { name: 'N62', desc: 'Android14,高性能,适配复杂场景', image: 'http://localhost:3001/uploads/menu/menu_n62.png' } + { name: 'N62', desc: 'Android11,高性能,适配复杂场景', image: '/uploads/menu/menu_n62.png' } ] }, { key: 'N6P', label: 'N6P', products: [ - { name: 'N6P', desc: 'Android14,高性能,适配复杂场景', image: 'http://localhost:3001/uploads/menu/menu_n6p.png' } + { name: 'N6P', desc: 'Android13,高性能,适配复杂场景', image: '/uploads/menu/menu_n6p.png' } ] }, { key: 'N6', label: 'N6', products: [ - { name: 'N6', desc: 'Android14,高性能,适配复杂场景', image: 'http://localhost:3001/uploads/menu/menu_n6.png' } + { name: 'N6', desc: 'Android7,高性能,适配复杂场景', image: '/uploads/menu/menu_n6.png' } ] }, ] @@ -88,7 +88,7 @@ const productMenu = [ key: 'k300', label: 'K300', products: [ - { name: 'K300', desc: '标准版,覆盖主流需求', image: 'http://localhost:3001/uploads/menu/menu_k300.png' } + { name: 'K300', desc: '标准版,覆盖主流需求', image: '/uploads/menu/menu_k300.png' } ] }, ] @@ -101,7 +101,7 @@ const productMenu = [ key: 'kd69', label: 'KD69', products: [ - { name: 'KD69', desc: '专业场景,扩展性强', image: 'http://localhost:3001/uploads/menu/menu_kd69.png' }, + { name: 'KD69', desc: '专业场景,扩展性强', image: '/uploads/menu/menu_kd69.png' }, ] }, ] @@ -116,11 +116,13 @@ const Navbar = () => { const [activeSub, setActiveSub] = useState(productMenu[0].subs[0].key); const productsRef = useRef(null); const [productsOpen, setProductsOpen] = useState(false); + const [productsSelected, setProductsSelected] = useState(false); useEffect(() => { const handleClickOutside = (e) => { if (productsRef.current && !productsRef.current.contains(e.target)) { setProductsOpen(false); + setProductsSelected(false); } }; document.addEventListener('mousedown', handleClickOutside); @@ -140,11 +142,7 @@ const Navbar = () => { const handleSearchKeyDown = (e) => { if (e.key === 'Enter') { - if (user) { - navigate('/aircraft', { state: { searchTerm: search } }); - } else { - navigate('/login'); - } + navigate('/'); } }; @@ -154,14 +152,23 @@ const Navbar = () => {
Logo -
- setProductsOpen((v) => !v)}>产品 +
+ { + setProductsOpen((prev) => { + const next = !prev; + setProductsSelected(next); + return next; + }); + }} + >产品
系列
@@ -224,23 +231,23 @@ const Navbar = () => {
-
+ {/*
解决方案
解决方案A 解决方案B 解决方案C
-
+
*/} -
+ {/*
行业
民航 通航 研发制造
-
+
*/}
diff --git a/src/pages/AircraftDetail.css b/src/pages/AircraftDetail.css deleted file mode 100755 index 8fed1d8..0000000 --- a/src/pages/AircraftDetail.css +++ /dev/null @@ -1,221 +0,0 @@ -.aircraft-detail-page { - min-height: 100vh; - background-color: #f5f5f5; -} - -.page-header { - background: white; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - margin-bottom: 20px; -} - -.header-content { - max-width: 1200px; - margin: 0 auto; - padding: 20px; - display: flex; - justify-content: space-between; - align-items: center; -} - -.header-content h1 { - margin: 0; - color: #333; - font-size: 24px; -} - -.aircraft-detail-card { - background: white; - border-radius: 8px; - padding: 32px; - margin-bottom: 24px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); -} - -.aircraft-header { - border-bottom: 2px solid #f0f0f0; - padding-bottom: 24px; - margin-bottom: 24px; -} - -.aircraft-code-large { - display: inline-block; - background: #1890ff; - color: white; - padding: 6px 16px; - border-radius: 6px; - font-size: 14px; - font-weight: 600; - margin-bottom: 12px; -} - -.aircraft-name-large { - margin: 0 0 16px 0; - color: #333; - font-size: 32px; - font-weight: 600; -} - -.aircraft-meta { - display: flex; - gap: 12px; -} - -.manufacturer-badge, -.type-badge { - padding: 6px 12px; - border-radius: 4px; - font-size: 14px; -} - -.manufacturer-badge { - background: #e6f7ff; - color: #1890ff; -} - -.type-badge { - background: #f6ffed; - color: #52c41a; -} - -.aircraft-description { - margin-bottom: 32px; -} - -.aircraft-description h3, -.aircraft-specifications h3 { - margin: 0 0 16px 0; - color: #333; - font-size: 20px; - font-weight: 600; -} - -.aircraft-description p { - color: #666; - line-height: 1.8; - font-size: 15px; -} - -.aircraft-specifications { - margin-top: 32px; -} - -.specs-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 20px; -} - -.spec-item { - display: flex; - flex-direction: column; - padding: 16px; - background: #fafafa; - border-radius: 6px; -} - -.spec-label { - color: #999; - font-size: 12px; - margin-bottom: 8px; - text-transform: uppercase; -} - -.spec-value { - color: #333; - font-size: 18px; - font-weight: 600; -} - -.pdf-materials-section { - background: white; - border-radius: 8px; - padding: 32px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); -} - -.section-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 24px; - flex-wrap: wrap; - gap: 16px; -} - -.section-header h3 { - margin: 0; - color: #333; - font-size: 20px; - font-weight: 600; -} - -.search-input-small { - padding: 8px 12px; - border: 1px solid #d9d9d9; - border-radius: 4px; - font-size: 14px; - min-width: 200px; -} - -.search-input-small:focus { - outline: none; - border-color: #1890ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); -} - -.pdf-materials-list { - display: flex; - flex-direction: column; - gap: 16px; -} - -.pdf-material-card { - display: flex; - justify-content: space-between; - align-items: center; - padding: 20px; - background: #fafafa; - border-radius: 6px; - border: 1px solid #e8e8e8; - transition: all 0.3s; -} - -.pdf-material-card:hover { - background: #f0f0f0; - border-color: #1890ff; -} - -.pdf-material-info { - flex: 1; -} - -.pdf-material-title { - margin: 0 0 8px 0; - color: #333; - font-size: 16px; - font-weight: 600; -} - -.pdf-material-description { - margin: 0 0 12px 0; - color: #666; - font-size: 14px; -} - -.pdf-material-meta { - display: flex; - gap: 20px; - color: #999; - font-size: 12px; -} - -.loading, -.error-state, -.empty-state { - text-align: center; - padding: 60px 20px; - color: #999; - font-size: 16px; -} - diff --git a/src/pages/AircraftDetail.js b/src/pages/AircraftDetail.js deleted file mode 100755 index f0b25d0..0000000 --- a/src/pages/AircraftDetail.js +++ /dev/null @@ -1,186 +0,0 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import { useParams, useNavigate } from 'react-router-dom'; -import { useAuth } from '../context/AuthContext'; -import axios from 'axios'; -import './AircraftDetail.css'; - -const AircraftDetail = () => { - const { id } = useParams(); - const navigate = useNavigate(); - const { logout } = useAuth(); - const [aircraft, setAircraft] = useState(null); - const [pdfMaterials, setPdfMaterials] = useState([]); - const [loading, setLoading] = useState(true); - const [searchTerm, setSearchTerm] = useState(''); - - const fetchAircraftDetail = useCallback(async () => { - try { - setLoading(true); - const response = await axios.get(`/api/aircraft/${id}`); - if (response.data.success) { - setAircraft(response.data.data); - } - } catch (error) { - console.error('获取机型详情失败:', error); - if (error.response?.status === 401) { - logout(); - navigate('/login'); - } - } finally { - setLoading(false); - } - }, [id, logout, navigate]); - - const fetchPdfMaterials = useCallback(async () => { - try { - const response = await axios.get(`/api/pdf/aircraft/${id}`, { - params: { search: searchTerm } - }); - if (response.data.success) { - setPdfMaterials(response.data.data); - } - } 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 { - const response = await axios.get(`/api/pdf/file/${materialId}`); - if (response.data.success) { - const fileUrl = `http://localhost:3001${response.data.data.fileUrl}`; - window.open(fileUrl, '_blank'); - } - } catch (error) { - console.error('打开PDF失败:', error); - alert('无法打开PDF文件,请确保文件存在'); - } - }; - - if (loading) { - return ( -
-
-
加载中...
-
-
- ); - } - - if (!aircraft) { - return ( -
-
-
机型信息不存在
-
-
- ); - } - - return ( -
-
-
- -

机型详情

-
-
-
-
-
{aircraft.code}
-

{aircraft.name}

-
- {aircraft.manufacturer} - {aircraft.type} -
-
- -
-

机型简介

-

{aircraft.description}

-
- -
-

技术规格

-
-
- 最大载客量 - {aircraft.specifications.maxPassengers} 人 -
-
- 最大航程 - {aircraft.specifications.maxRange} -
-
- 巡航速度 - {aircraft.specifications.cruiseSpeed} -
-
- 机长 - {aircraft.specifications.length} -
-
- 翼展 - {aircraft.specifications.wingspan} -
-
-
-
- -
-
-

相关资料

- setSearchTerm(e.target.value)} - className="search-input-small" - /> -
- - {pdfMaterials.length === 0 ? ( -
暂无相关资料
- ) : ( -
- {pdfMaterials.map((material) => ( -
-
-

{material.title}

-

{material.description}

-
- 上传日期: {material.uploadDate} - 文件大小: {material.fileSize} -
-
- -
- ))} -
- )} -
-
-
- ); -}; - -export default AircraftDetail; - diff --git a/src/pages/AircraftList.css b/src/pages/AircraftList.css deleted file mode 100755 index ccac66c..0000000 --- a/src/pages/AircraftList.css +++ /dev/null @@ -1,142 +0,0 @@ -.aircraft-list-page { - min-height: 100vh; - background-color: #f5f5f5; -} - -.page-header { - background: white; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - margin-bottom: 20px; -} - -.header-content { - max-width: 1200px; - margin: 0 auto; - padding: 20px; - display: flex; - justify-content: space-between; - align-items: center; -} - -.header-content h1 { - margin: 0; - color: #333; - font-size: 24px; -} - -.header-actions { - display: flex; - align-items: center; - gap: 15px; -} - -.user-info { - color: #666; - font-size: 14px; -} - -.search-section { - margin-bottom: 24px; -} - -.search-input { - width: 100%; - max-width: 600px; - padding: 12px 16px; - border: 1px solid #d9d9d9; - border-radius: 6px; - font-size: 14px; - transition: all 0.3s; -} - -.search-input:focus { - outline: none; - border-color: #1890ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); -} - -.loading { - text-align: center; - padding: 40px; - color: #999; -} - -.aircraft-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 20px; -} - -.aircraft-card { - background: white; - border-radius: 8px; - padding: 24px; - cursor: pointer; - transition: all 0.3s; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - position: relative; - border: 2px solid transparent; -} - -.aircraft-card:hover { - transform: translateY(-4px); - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); - border-color: #1890ff; -} - -.aircraft-code { - display: inline-block; - background: #1890ff; - color: white; - padding: 4px 12px; - border-radius: 4px; - font-size: 12px; - font-weight: 600; - margin-bottom: 12px; -} - -.aircraft-name { - margin: 0 0 12px 0; - color: #333; - font-size: 20px; - font-weight: 600; -} - -.aircraft-info { - display: flex; - flex-direction: column; - gap: 6px; - color: #666; - font-size: 14px; -} - -.manufacturer { - font-weight: 500; -} - -.type { - color: #999; -} - -.card-arrow { - position: absolute; - right: 24px; - top: 50%; - transform: translateY(-50%); - font-size: 24px; - color: #d9d9d9; - transition: all 0.3s; -} - -.aircraft-card:hover .card-arrow { - color: #1890ff; - transform: translateY(-50%) translateX(4px); -} - -.empty-state { - text-align: center; - padding: 60px 20px; - color: #999; - font-size: 16px; -} - diff --git a/src/pages/AircraftList.js b/src/pages/AircraftList.js deleted file mode 100755 index 1f089d7..0000000 --- a/src/pages/AircraftList.js +++ /dev/null @@ -1,89 +0,0 @@ -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'; - -const AircraftList = () => { - const [aircrafts, setAircrafts] = useState([]); - const [loading, setLoading] = useState(true); - const location = useLocation(); - const initialSearch = location.state?.searchTerm || ''; - const [searchTerm, setSearchTerm] = useState(initialSearch); - const { logout } = useAuth(); - const navigate = useNavigate(); - - const fetchAircrafts = useCallback(async () => { - try { - setLoading(true); - const response = await axios.get('/api/aircraft/list', { - params: { search: searchTerm } - }); - if (response.data.success) { - setAircrafts(response.data.data); - } - } catch (error) { - console.error('获取机型列表失败:', error); - if (error.response?.status === 401) { - logout(); - navigate('/login'); - } - } finally { - setLoading(false); - } - }, [searchTerm, logout, navigate]); - - useEffect(() => { - fetchAircrafts(); - }, [fetchAircrafts]); - - const handleAircraftClick = (id) => { - navigate(`/aircraft/${id}`); - }; - - return ( -
-
-

通信技术部

-
- setSearchTerm(e.target.value)} - className="search-input" - /> -
- - {loading ? ( -
加载中...
- ) : ( -
- {aircrafts.length === 0 ? ( -
未找到机型信息
- ) : ( - aircrafts.map((aircraft) => ( -
handleAircraftClick(aircraft.id)} - > -
{aircraft.code}
-

{aircraft.name}

-
- {aircraft.manufacturer} - {aircraft.type} -
-
-
- )) - )} -
- )} -
-
- ); -}; - -export default AircraftList; - diff --git a/src/pages/Home.js b/src/pages/Home.js index 563cde0..ba86b35 100755 --- a/src/pages/Home.js +++ b/src/pages/Home.js @@ -5,26 +5,26 @@ const slidesData = [ { id: 1, title: '行业资讯:新机型发布', - description: '最新机型亮相航空展,性能全面升级', - image: 'http://localhost:3001/uploads/menu/home_1.png' + description: '最新机型亮相展览,性能全面升级', + image: '/uploads/menu/home_1.jpg' }, { id: 2, title: '解决方案:资料管理优化', description: '一体化资料管理平台上线,提效30%', - image: 'http://localhost:3001/uploads/menu/home_2.png' + image: '/uploads/menu/home_2.jpg' }, { id: 3, title: '产品更新:PDF在线预览', description: '新增在线预览与多端适配功能', - image: 'http://localhost:3001/uploads/menu/home_3.png' + image: '/uploads/menu/home_3.jpg' }, { id: 4, title: '客户案例:数字化转型', description: '多行业落地实践,推动数据驱动决策', - image: 'http://localhost:3001/uploads/menu/home_4.png' + image: '/uploads/menu/home_4.jpg' } ]; diff --git a/src/pages/Login.js b/src/pages/Login.js index 1ef20c9..b3a28cf 100755 --- a/src/pages/Login.js +++ b/src/pages/Login.js @@ -18,11 +18,11 @@ const Login = () => { const result = await login(username, password); - if (result.success) { - navigate('/aircraft'); - } else { - setError(result.error); - } + if (result.success) { + navigate('/'); + } else { + setError(result.error); + } setLoading(false); }; diff --git a/src/pages/ProductDetail.css b/src/pages/ProductDetail.css index 71467b8..8d35a44 100644 --- a/src/pages/ProductDetail.css +++ b/src/pages/ProductDetail.css @@ -15,14 +15,16 @@ .product-title { margin: 0; color: #333; font-size: 22px; font-weight: 600; } .product-desc { color: #666; } .specs-section { margin-top: 24px; } -.specs-body { margin-top: 12px; background: #f6ffed; border: 1px solid #d9f7be; border-radius: 0; } -.specs-group { margin: 12px 0; } -.specs-group-header { width: 100%; text-align: left; padding: 10px 12px; border: 1px solid #d9f7be; border-radius: 4px; background: #f6ffed; cursor: pointer; font-weight: 600; } -.specs-summary { padding: 12px 14px; border-bottom: 1px solid #f0f0f0; } -.specs-table { width: 100%; border-collapse: collapse; border: 1px solid #d9d9d9; } -.specs-table td { padding: 10px 14px; border: 1px solid #e8e8e8; } -.specs-key { width: 180px; color: #666; } -.specs-val { color: #333; } +.specs-body { margin-top: 0; background: #fff; border: none; border-radius: 0; padding: 0; } +.specs-group { margin: 0; } +.specs-group-header { width: 100%; text-align: left; padding: 18px 0; border: none; background: transparent; cursor: pointer; font-weight: 700; color: #111; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #e8e8e8; } +.specs-summary { padding: 12px 0; border-bottom: 1px solid #e8e8e8; } +.specs-table { width: 100%; border-collapse: collapse; border: none; background: #fff; } +.specs-table tr { border-bottom: 1px solid #e8e8e8; } +.specs-table tr:last-child { border-bottom: none; } +.specs-table td { padding: 12px 14px; border: none; } +.specs-key { width: 220px; color: #666; text-align: left; } +.specs-val { color: #1890ff; text-align: center; } .error-state { text-align: center; padding: 60px 20px; color: #999; } .docs-section { margin-top: 16px; } .docs-list { list-style: none; padding: 0 8px 12px; margin: 0; } @@ -37,7 +39,36 @@ .tag-specs { background: #f6ffed; color: #52c41a; } .tag-docs { background: #fff7e6; color: #fa8c16; } .carousel-full .section-tag { position: absolute; top: 12px; left: 12px; margin: 0; z-index: 2; } -.specs-table tr:nth-child(even) { background: #fafafa; } .docs-item:hover { background: #fafafa; } -.docs-body { background: #fff7e6; border: 1px solid #ffd591; border-radius: 0; padding: 8px 8px; } +.docs-body { background: #f5f5f5; border: none; border-radius: 0; padding: 8px 8px; } + +.pd-title { text-align: center; font-size: 24px; font-weight: 700; color: #111; padding: 16px 0; } +.pd-panel { margin: 12px 0; } +.pd-panel { border: none; border-radius: 0; background: #fff; } +.pd-panel-header { width: 100%; text-align: left; padding: 12px 0; background: transparent; cursor: pointer; font-weight: 700; color: #111; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #e8e8e8; } +.pd-panel-body { background: #fff; padding: 12px 0; } +.pd-panel.expanded { border: 1px dashed #bfbfbf; border-radius: 6px; } +.pd-panel.expanded .pd-panel-header { padding: 12px 14px; border-bottom: none; } +.image-row { display: flex; justify-content: center; align-items: center; gap: 40px; padding: 12px; } +.image-row img { height: 420px; width: auto; object-fit: contain; display: block; } +.size-carousel { position: relative; background: #fff; overflow: hidden; width: 100%; min-height: 420px; border-radius: 0; } +.size-slides { display: grid; grid-template-columns: 100%; } +.size-slide { opacity: 0; transition: opacity 0.3s ease; display: none; } +.size-slide.active { opacity: 1; display: block; } +.size-slide-img { width: 100%; height: 420px; object-fit: contain; display: block; } +.size-arrow { position: absolute; top: 50%; transform: translateY(-50%); background: rgba(255,255,255,0.85); border: 1px solid #e8e8e8; border-radius: 50%; width: 32px; height: 32px; cursor: pointer; } +.size-arrow.left { left: 8px; } +.size-arrow.right { right: 8px; } +.size-indicators { display: flex; justify-content: center; gap: 8px; margin-top: 8px; } +.size-dot { width: 8px; height: 8px; border-radius: 50%; background: #d9d9d9; cursor: pointer; } +.size-dot.active { background: #1890ff; } +.device-figure { position: relative; display: flex; align-items: center; justify-content: center; } +.device-title { position: absolute; top: -26px; left: 50%; transform: translateX(-50%); font-weight: 700; color: #111; } +.diag-line { position: absolute; left: 10%; top: 10%; width: 80%; height: 0; border-top: 2px solid rgba(255,255,255,0.9); transform: rotate(-35deg); } +.diag-label { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); color: #fff; font-weight: 700; text-shadow: 0 1px 2px rgba(0,0,0,0.6); } +.device-figure.stylus .diag-line, .device-figure.stylus .diag-label, .device-figure.stylus .device-title { display: none; } +.pd-list { margin-top: 24px; } +.pd-list-item { width: 100%; text-align: left; background: transparent; border: none; border-bottom: 1px solid #e8e8e8; padding: 18px 0; font-weight: 700; color: #111; display: flex; justify-content: space-between; align-items: center; } +.pd-list-body { padding: 12px 0; } +.specs-sublist { padding-left: 28px; } diff --git a/src/pages/ProductDetail.js b/src/pages/ProductDetail.js index f2d3d67..edb7117 100644 --- a/src/pages/ProductDetail.js +++ b/src/pages/ProductDetail.js @@ -12,6 +12,12 @@ const ProductDetail = () => { const [sections, setSections] = useState([]); const [expanded, setExpanded] = useState({}); const [specsLoading, setSpecsLoading] = useState(false); + const [imgOpen, setImgOpen] = useState(true); + const [highlightsOpen, setHighlightsOpen] = useState(false); + const [compareOpen, setCompareOpen] = useState(true); + const [promoOpen, setPromoOpen] = useState(false); + const [faqOpen, setFaqOpen] = useState(false); + const [reviewOpen, setReviewOpen] = useState(false); useEffect(() => { const srcs = product @@ -29,10 +35,10 @@ const ProductDetail = () => { const loadImages = async () => { try { const qs = new URLSearchParams({ series, sub }).toString(); - const res = await fetch(`http://localhost:3001/api/docs/images?${qs}`); + const res = await fetch(`/api/docs/images?${qs}`); const data = await res.json(); if (data.success && Array.isArray(data.data) && data.data.length) { - setImages(data.data.map((d) => `http://localhost:3001${d.url}`)); + setImages(data.data.map((d) => `${d.url}`)); } } catch {} }; @@ -44,17 +50,17 @@ const ProductDetail = () => { try { setSpecsLoading(true); const qs = new URLSearchParams({ series, sub }).toString(); - const res = await fetch(`http://localhost:3001/api/docs/specs?${qs}`); + const res = await fetch(`/api/docs/specs?${qs}`); const data = await res.json(); if (data.success && Array.isArray(data.data)) { if (data.data.length && typeof data.data[0]?.title === 'string' && Array.isArray(data.data[0]?.rows)) { setSections(data.data); setSpecs([]); - setExpanded({}); + setExpanded(Object.fromEntries(data.data.map((_, i) => [i, true]))); } else { setSections([{ title: '参数', rows: data.data }]); setSpecs(data.data); - setExpanded({}); + setExpanded({ 0: true }); } } } catch {} @@ -89,90 +95,129 @@ const ProductDetail = () => { return (
-
-
机型图片
- {images.length > 1 && } - -
- {images.map((src, idx) => ( -
- {`${product.name}-${idx+1}`} -
- ))} -
- - {images.length > 1 && } - - {images.length > 1 && ( -
- {images.map((_, idx) => ( - setActive(idx)} - /> - ))} -
- )} -
- -
-
-
详细参数
- {specsLoading ? ( -
加载中...
- ) : sections.length ? ( - sections.map((sec, i) => { - const isOpen = !!expanded[i]; - const toggle = () => setExpanded((prev) => ({ ...prev, [i]: !prev[i] })); - return ( -
- - {isOpen && ( -
- - - {sec.rows.length ? ( - sec.rows.map((row) => { - const k = String(row.key || ''); - const v = String(row.value || ''); - const kb = /^\*\*.*\*\*$/.test(k) || k.startsWith('!'); - const vb = /^\*\*.*\*\*$/.test(v) || v.startsWith('!'); - const bold = kb || vb; - const cleanKey = k.replace(/^!/, '').replace(/^\*\*/, '').replace(/\*\*$/, ''); - const cleanVal = v.replace(/^!/, '').replace(/^\*\*/, '').replace(/\*\*$/, ''); - return ( - - - - - ); - }) - ) : ( - - )} - -
{cleanKey}{cleanVal}
暂无参数
-
- )} +
{product.name}{product.desc ? ` | ${product.desc}` : ''}
+
+
+ + {imgOpen && ( +
+ {images.length > 1 ? ( +
+
+ {images.map((src, idx) => ( +
+ {`${product.name}-${idx+1}`} +
+ ))} +
+ + +
+ {images.map((_, idx) => ( + setActive(idx)} + /> + ))} +
- ); - }) - ) : ( -
暂无参数
+ ) : ( +
+ {(images.length ? images : (product?.image ? [product.image] : [])).map((src, idx) => ( + {`${product.name}-${idx+1}`} + ))} +
+ )} +
+ )} + + {highlightsOpen &&
} + + {compareOpen && ( +
+
+ {specsLoading ? ( +
加载中...
+ ) : sections.length ? ( + sections.map((sec, i) => { + const isOpen = !!expanded[i]; + const toggle = () => setExpanded((prev) => ({ ...prev, [i]: !prev[i] })); + return ( +
+ + {isOpen && ( +
+ + + {sec.rows.length ? ( + sec.rows.map((row) => { + const k = String(row.key || ''); + const v = String(row.value || ''); + const kb = /^\*\*.*\*\*$/.test(k) || k.startsWith('!'); + const vb = /^\*\*.*\*\*$/.test(v) || v.startsWith('!'); + const bold = kb || vb; + const cleanKey = k.replace(/^!/, '').replace(/^\*\*/, '').replace(/\*\*$/, ''); + const cleanVal = v.replace(/^!/, '').replace(/^\*\*/, '').replace(/\*\*$/, ''); + return ( + + + + + ); + }) + ) : ( + + )} + +
{cleanKey}{cleanVal}
暂无参数
+
+ )} +
+ ); + }) + ) : ( +
暂无参数
+ )} +
+
+ )} + {/* */} + {promoOpen &&
} + + {faqOpen &&
} + + {reviewOpen && ( +
+
+ +
+
)}
-
-
-
资料下载
-
- -
-
-
+ +
); @@ -189,7 +234,7 @@ const DocsList = ({ series, sub }) => { setLoading(true); setError(''); const qs = new URLSearchParams({ series, sub }).toString(); - const res = await fetch(`http://localhost:3001/api/docs/list?${qs}`); + const res = await fetch(`/api/docs/list?${qs}`); const data = await res.json(); if (data.success) { setDocs(data.data || []); @@ -214,11 +259,11 @@ const DocsList = ({ series, sub }) => {