diff --git a/app/components/header/Header.tsx b/app/components/header/Header.tsx
new file mode 100644
index 0000000..a707a6e
--- /dev/null
+++ b/app/components/header/Header.tsx
@@ -0,0 +1,34 @@
+import { useEffect, useState } from "react";
+
+export function Header(){
+ const [currentTime, setCurrentTime] = useState(new Date());
+
+ useEffect(() => {
+ const timer = setInterval(() => {
+ setCurrentTime(new Date());
+ }, 1000); // 每秒更新一次
+
+ // 清理定时器
+ return () => clearInterval(timer);
+ }, []);
+
+ return (
+
+ {/* 时间显示 */}
+
+
+ {currentTime.toLocaleString('zh-CN')}
+
+
+
+
console.log('登录')}>登录
+
+
+
console.log('注册')}>注册
+
+
+ )
+}
\ No newline at end of file
diff --git a/app/components/header/TopNav.tsx b/app/components/header/TopNav.tsx
new file mode 100644
index 0000000..f5dfd1f
--- /dev/null
+++ b/app/components/header/TopNav.tsx
@@ -0,0 +1,105 @@
+import { Search } from 'lucide-react';
+import React, { useState } from 'react';
+
+interface MenuItem {
+ label: string;
+ key: string;
+}
+
+// 定义 TopNavProps 接口,描述组件的 props 类型
+// menuItems? 菜单项数组
+// activeKey: 当前激活的菜单项
+// onSearch: 搜索回调函数
+// onItemClick: 菜单点击回调函数
+interface TopNavProps {
+ menuItems?: MenuItem[];
+ activeKey?: string;
+ onSearch?: (keyword: string) => void;
+ onItemClick?: (key: string) => void;
+}
+
+//定义 TopNav 组件,类型为 React 函数组件,接收 TopNavProps 类型的 props
+//解构并设置 menuItems 默认值,如果父组件没有传入则使用默认的6个菜单项
+// activeKey: 当前激活的菜单项
+// onSearch: 搜索回调函数
+// onItemClick: 菜单点击回调函数
+export function TopNav({
+ menuItems = [
+ { label: '首页', key: 'home' },
+ { label: '烽火动态', key: 'news' },
+ { label: '烽火铸魂', key: 'soul' },
+ { label: '烽火训练', key: 'training' },
+ { label: '联系热线', key: 'hotline' },
+ { label: '综合服务', key: 'service' },
+ ],
+ activeKey: externalActiveKey, // 从外部传入的 activeKey
+ onSearch,
+ onItemClick,
+}: TopNavProps){
+ // 使用外部传入的 activeKey,如果没有则使用内部状态
+ // 创建内部状态 internalActiveKey,默认值为 'home',用于内部管理激活状态
+ const [internalActiveKey, setInternalActiveKey] = useState('home');
+ // 如果外部传入了 activeKey 则使用外部的,否则使用内部状态(支持受控和非受控模式)
+ const currentActiveKey = externalActiveKey !== undefined ? externalActiveKey : internalActiveKey;
+ // 创建搜索关键词状态,初始为空字符串
+ const [searchKeyword, setSearchKeyword] = useState('');
+ // 处理搜索提交事件, 阻止默认表单提交行为,调用搜索回调函数
+ const handleSearchSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ onSearch?.(searchKeyword); // .? 可选链操作符,确保 onSearch 存在时才调用
+ };
+
+ // 定义菜单项点击处理函数,如果外部没有传入 activeKey 则更新内部状态,调用点击回调函数
+ const handleItemClick = (item: MenuItem) => {
+ // 更新内部状态(如果使用内部状态)
+ if (externalActiveKey === undefined) {
+ setInternalActiveKey(item.key);
+ }
+ // 调用外部回调函数
+ onItemClick?.(item.key); // .? 可选链操作符,确保 onItemClick 存在时才调用
+ };
+
+ return (
+
+ {/* 搜索框与导航菜单组合 */}
+
+ {/* 搜索框 */}
+
+
+ {/* 导航菜单 */}
+
+ {menuItems.map((item) => {
+ const isActive = currentActiveKey === item.key; // 判断当前项是否激活
+ return (
+ -
+
+
+ );
+ })}
+
+
+
+ );
+};
diff --git a/app/components/untils/Carousel.tsx b/app/components/untils/Carousel.tsx
index fabc5f2..e9eff99 100755
--- a/app/components/untils/Carousel.tsx
+++ b/app/components/untils/Carousel.tsx
@@ -2,7 +2,7 @@
import * as React from "react";
import Autoplay from "embla-carousel-autoplay";
-import { Card, CardContent } from "@/components/ui/card";
+import { Card, CardContent } from "@/ui/card";
import {
Carousel,
CarouselContent,
@@ -10,7 +10,7 @@ import {
CarouselNext,
CarouselPrevious,
type CarouselApi,
-} from "@/components/ui/carousel";
+} from "@/ui/carousel";
export function CarouselDemo() {
const [api, setApi] = React.useState();
diff --git a/app/images/header.png b/app/images/header.png
new file mode 100644
index 0000000..75a8636
Binary files /dev/null and b/app/images/header.png differ
diff --git a/app/routes/news.tsx b/app/routes/news.tsx
index 42308ab..743cbbd 100755
--- a/app/routes/news.tsx
+++ b/app/routes/news.tsx
@@ -1,5 +1,5 @@
import { CarouselDemo } from "@/components/untils/Carousel";
-import NewsList from "@/components/list/NewsList";
+import type { Route } from "./+types/news";
export function meta( ) {
@@ -10,5 +10,5 @@ export function meta( ) {
}
export default function Home() {
- return ;
+ return ;
}
diff --git a/app/components/ui/badge.tsx b/app/ui/badge.tsx
similarity index 100%
rename from app/components/ui/badge.tsx
rename to app/ui/badge.tsx
diff --git a/app/components/ui/button.tsx b/app/ui/button.tsx
similarity index 100%
rename from app/components/ui/button.tsx
rename to app/ui/button.tsx
diff --git a/app/components/ui/card.tsx b/app/ui/card.tsx
similarity index 100%
rename from app/components/ui/card.tsx
rename to app/ui/card.tsx
diff --git a/app/components/ui/carousel.tsx b/app/ui/carousel.tsx
similarity index 99%
rename from app/components/ui/carousel.tsx
rename to app/ui/carousel.tsx
index 71cff4c..99a04c7 100644
--- a/app/components/ui/carousel.tsx
+++ b/app/ui/carousel.tsx
@@ -5,7 +5,7 @@ import useEmblaCarousel, {
import { ArrowLeft, ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils"
-import { Button } from "@/components/ui/button"
+import { Button } from "@/ui/button"
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters
diff --git a/app/components/ui/dialog.tsx b/app/ui/dialog.tsx
similarity index 100%
rename from app/components/ui/dialog.tsx
rename to app/ui/dialog.tsx
diff --git a/app/components/ui/input.tsx b/app/ui/input.tsx
similarity index 100%
rename from app/components/ui/input.tsx
rename to app/ui/input.tsx
diff --git a/app/components/ui/label.tsx b/app/ui/label.tsx
similarity index 100%
rename from app/components/ui/label.tsx
rename to app/ui/label.tsx
diff --git a/app/components/ui/select.tsx b/app/ui/select.tsx
similarity index 100%
rename from app/components/ui/select.tsx
rename to app/ui/select.tsx
diff --git a/app/components/ui/sonner.tsx b/app/ui/sonner.tsx
similarity index 100%
rename from app/components/ui/sonner.tsx
rename to app/ui/sonner.tsx
diff --git a/app/components/ui/table.tsx b/app/ui/table.tsx
similarity index 100%
rename from app/components/ui/table.tsx
rename to app/ui/table.tsx