This commit is contained in:
Li1304553726 2025-11-19 11:45:03 +08:00
parent 6b1e929482
commit bb43c4312e
1 changed files with 41 additions and 33 deletions

View File

@ -1,16 +1,11 @@
import { Search } from 'lucide-react'; import { Search } from 'lucide-react';
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
interface MenuItem { interface MenuItem {
label: string; label: string;
key: string; key: string;
} }
// 定义 TopNavProps 接口,描述组件的 props 类型
// menuItems? 菜单项数组
// activeKey: 当前激活的菜单项
// onSearch: 搜索回调函数
// onItemClick: 菜单点击回调函数
interface TopNavProps { interface TopNavProps {
menuItems?: MenuItem[]; menuItems?: MenuItem[];
activeKey?: string; activeKey?: string;
@ -18,11 +13,6 @@ interface TopNavProps {
onItemClick?: (key: string) => void; onItemClick?: (key: string) => void;
} }
//定义 TopNav 组件,类型为 React 函数组件,接收 TopNavProps 类型的 props
//解构并设置 menuItems 默认值如果父组件没有传入则使用默认的6个菜单项
// activeKey: 当前激活的菜单项
// onSearch: 搜索回调函数
// onItemClick: 菜单点击回调函数
export function TopNav({ export function TopNav({
menuItems = [ menuItems = [
{ label: '首页', key: 'home' }, { label: '首页', key: 'home' },
@ -32,35 +22,37 @@ export function TopNav({
{ label: '联系热线', key: 'hotline' }, { label: '联系热线', key: 'hotline' },
{ label: '综合服务', key: 'service' }, { label: '综合服务', key: 'service' },
], ],
activeKey: externalActiveKey, // 从外部传入的 activeKey activeKey: externalActiveKey,
onSearch, onSearch,
onItemClick, onItemClick,
}: TopNavProps){ }: TopNavProps){
// 使用外部传入的 activeKey如果没有则使用内部状态
// 创建内部状态 internalActiveKey默认值为 'home',用于内部管理激活状态
const [internalActiveKey, setInternalActiveKey] = useState('home'); const [internalActiveKey, setInternalActiveKey] = useState('home');
// 如果外部传入了 activeKey 则使用外部的,否则使用内部状态(支持受控和非受控模式)
const currentActiveKey = externalActiveKey !== undefined ? externalActiveKey : internalActiveKey; const currentActiveKey = externalActiveKey !== undefined ? externalActiveKey : internalActiveKey;
// 创建搜索关键词状态,初始为空字符串
const [searchKeyword, setSearchKeyword] = useState(''); const [searchKeyword, setSearchKeyword] = useState('');
// 处理搜索提交事件, 阻止默认表单提交行为,调用搜索回调函数 const [activeIndex, setActiveIndex] = useState(0);
const [prevIndex, setPrevIndex] = useState(0);
// 监听激活项变化,更新索引和动画方向
useEffect(() => {
const currentIndex = menuItems.findIndex(item => item.key === currentActiveKey);
setPrevIndex(activeIndex);
setActiveIndex(currentIndex);
}, [currentActiveKey, menuItems, activeIndex]);
const handleSearchSubmit = (e: React.FormEvent) => { const handleSearchSubmit = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
onSearch?.(searchKeyword); // .? 可选链操作符,确保 onSearch 存在时才调用 onSearch?.(searchKeyword);
}; };
// 定义菜单项点击处理函数,如果外部没有传入 activeKey 则更新内部状态,调用点击回调函数
const handleItemClick = (item: MenuItem) => { const handleItemClick = (item: MenuItem) => {
// 更新内部状态(如果使用内部状态)
if (externalActiveKey === undefined) { if (externalActiveKey === undefined) {
setInternalActiveKey(item.key); setInternalActiveKey(item.key);
} }
// 调用外部回调函数 onItemClick?.(item.key);
onItemClick?.(item.key); // .? 可选链操作符,确保 onItemClick 存在时才调用
}; };
return ( return (
<div className="h-14 flex items-center justify-center px-8 bg-white"> <div className="h-20 flex items-center justify-center px-8 bg-white">
{/* 搜索框与导航菜单组合 */} {/* 搜索框与导航菜单组合 */}
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
{/* 搜索框 */} {/* 搜索框 */}
@ -71,26 +63,42 @@ export function TopNav({
value={searchKeyword} value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)} onChange={(e) => setSearchKeyword(e.target.value)}
placeholder="搜索..." placeholder="搜索..."
className="pl-10 pr-4 py-2 text-sm rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent w-64 transition-all duration-200 hover:shadow-sm" className="pl-10 pr-4 py-2 text-sm border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent w-64 transition-all duration-200 hover:shadow-sm"
/> />
<span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"> <span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400">
<Search className="w-5 h-5" /> <Search className="w-5 h-5" />
</span> </span>
</div> </div>
</form> </form>
{/* 导航菜单 */} {/* 导航菜单 */}
<ul className="flex space-x-2"> <ul className="flex space-x-8 relative">
{menuItems.map((item) => { {/* 滑动背景层 */}
const isActive = currentActiveKey === item.key; // 判断当前项是否激活 <div
className="absolute inset-y-0 z-0 transition-all duration-500 ease-out"
style={{
left: `${activeIndex * (100 / menuItems.length)}%`,
width: `${100 / menuItems.length}%`,
clipPath: 'polygon(0% 0%, 80% 0%, 100% 100%, 20% 100%)', // 向左倾斜的平行四边形
background: 'linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)',
transform: `translateX(${(prevIndex - activeIndex) * 100}%)`,
}}
/>
{menuItems.map((item, index) => {
const isActive = currentActiveKey === item.key;
return ( return (
<li key={item.key}> <li
key={item.key}
className="relative z-10"
style={{ width: `${100 / menuItems.length}%` }}
>
<button <button
onClick={() => handleItemClick(item)} onClick={() => handleItemClick(item)}
className={`px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200 ${ className={`w-full h-full px-4 py-2 text-2xl transition-all duration-300 ${
isActive isActive
? 'bg-blue-600 text-white shadow-md' // 激活状态样式 ? 'text-white' // 激活状态文字颜色
: 'text-gray-600 hover:bg-blue-100 hover:text-blue-700' // 非激活状态样式 : 'text-gray-600 hover:text-black cursor-pointer' // 非激活状态样式
}`} }`}
> >
{item.label} {item.label}
@ -102,4 +110,4 @@ export function TopNav({
</div> </div>
</div> </div>
); );
}; };