diff --git a/app/components/Carousel.tsx b/app/components/Carousel.tsx new file mode 100755 index 0000000..0ea29e9 --- /dev/null +++ b/app/components/Carousel.tsx @@ -0,0 +1,92 @@ +// src/components/CarouselDemo.tsx +import * as React from "react"; +import Autoplay from "embla-carousel-autoplay"; + +import { Card, CardContent } from "@/ui/card"; +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, + type CarouselApi, +} from "@/ui/carousel"; + const imageUrls = [ + + "/images/carousel-1.jpg", + "/images/carousel-2.jpg", + "/images/carousel-3.jpg", + "/images/carousel-4.jpg", + "/images/carousel-5.jpg", + "/images/carousel-6.jpg", +]; +export function CarouselDemo() { + const [api, setApi] = React.useState(); + const [current, setCurrent] = React.useState(0); + const [count, setCount] = React.useState(0); + + const totalSlides = imageUrls.length; + + React.useEffect(() => { + if (!api) return; + + setCount(api.scrollSnapList().length); + setCurrent(api.selectedScrollSnap()); + + api.on("select", () => { + setCurrent(api.selectedScrollSnap()); + }); + }, [api]); + + return ( +
+ + + {imageUrls.map((src, index) => ( + +
+ + + {`Slide + + +
+
+ ))} +
+ + +
+ + {/* 分页指示器 - 右下角 */} +
+ {Array.from({ length: count }).map((_, index) => ( +
+
+ ); +} \ No newline at end of file diff --git a/app/components/body/ImageGridSection.tsx b/app/components/body/ImageGridSection.tsx new file mode 100644 index 0000000..6625297 --- /dev/null +++ b/app/components/body/ImageGridSection.tsx @@ -0,0 +1,103 @@ +import { CarouselDemo } from "@/components/Carousel"; +const ImageGridSection = () => { + // 替换为你自己的图片路径 + const elements = [ + "/images/carousel-1.jpg", + , + "/images/carousel-2.jpg", + "/images/carousel-3.jpg" + ]; + const listItems = [ + '新闻标题一:重要政策发布', + '新闻标题二:经济数据稳步回升', + '新闻标题三:科技创新成果显著', + '新闻标题四:民生工程持续推进', + '新闻标题五:国际交流合作深化' + ]; + return ( +
+ + {/* 左侧:3张图片 + 1个轮播图 2x2 网格 */} +
+ {elements.map((element, index) => ( +
+ {typeof element === 'string' ? ( + {`图片 + ) : ( + element // 如果是 React 元素,则直接渲染 + )} +
+ ))} +
+
+
    + {listItems.map((item, index) => ( +
  • + + {item} +
  • + ))} +
+
+
+ ); +}; +export default ImageGridSection; \ No newline at end of file diff --git a/app/components/header/Header.tsx b/app/components/header/Header.tsx new file mode 100644 index 0000000..1714f94 --- /dev/null +++ b/app/components/header/Header.tsx @@ -0,0 +1,35 @@ +import { useEffect, useState } from "react"; + +export function Header(){ + const [currentTime, setCurrentTime] = useState(new Date()); + + useEffect(() => { + // setInterval是 JavaScript 中的一个全局函数,用于重复执行代码 + const timer = setInterval(() => { + setCurrentTime(new Date()); + }, 1000); // 每秒更新一次 + + // 清理定时器 + return () => clearInterval(timer);// 只在组件卸载时清理定时器 + }, []); // 只在组件首次挂载时设置定时器 + + return ( +
+ {/* 时间显示 只显示日期: "2025/3/15"*/} +
+
+ {currentTime.toLocaleDateString('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 ( +
+ {/* 搜索框与导航菜单组合 */} +
+ {/* 搜索框 */} +
+
+ setSearchKeyword(e.target.value)} + 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" + /> + + + +
+
+ + {/* 导航菜单 */} +
    + {menuItems.map((item) => { + const isActive = currentActiveKey === item.key; // 判断当前项是否激活 + return ( +
  • + +
  • + ); + })} +
+
+
+ ); +}; diff --git a/app/components/list/NewsData.tsx b/app/components/list/NewsData.tsx new file mode 100644 index 0000000..4d22885 --- /dev/null +++ b/app/components/list/NewsData.tsx @@ -0,0 +1,29 @@ +export interface News { + id: string; + type: string; + title: string; + time: string; + url?: string; +} + +// 导出生成的模拟新闻数据 +export const NewsData = (): News[] => { + return [ + { id: "news-1", type: "科技", title: "人工智能技术助力医疗诊断", time: "2023-11-01", url: "https://www.baidu.com" }, + { id: "news-2", type: "科技", title: "全球气候变化峰会达成新协议", time: "2023-11-02", url: "https://www.baidu.com" }, + { id: "news-3", type: "科技", title: "新能源汽车市场持续增长", time: "2023-11-03", url: "https://www.baidu.com" }, + { id: "news-4", type: "科技", title: "量子计算研究取得突破性进展", time: "2023-11-04", url: "https://www.baidu.com" }, + { id: "news-5", type: "科技", title: "国际空间站完成新一轮科学实验", time: "2023-11-05", url: "https://www.baidu.com" }, + { id: "news-6", type: "科技", title: "数字货币监管政策逐步完善", time: "2023-11-06", url: "https://www.baidu.com" }, + { id: "news-7", type: "科技", title: "5G网络覆盖范围进一步扩大", time: "2023-11-07", url: "https://www.baidu.com" }, + { id: "news-8", type: "科技", title: "教育公平问题引发社会关注", time: "2023-11-08", url: "https://www.baidu.com" }, + { id: "news-9", type: "科技", title: "电影《星际穿越》重映引发热潮", time: "2023-11-09", url: "https://www.baidu.com" } + + + + + ]; +}; + +// 默认导出模拟新闻数据 +export const mockNewsData = NewsData(); \ No newline at end of file diff --git a/app/components/list/NewsList.tsx b/app/components/list/NewsList.tsx new file mode 100644 index 0000000..bdc5959 --- /dev/null +++ b/app/components/list/NewsList.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { mockNewsData } from './NewsData'; // 导入新闻数据 + +interface NewsProps { + title?: string; + description?: string; + time?: string; + type?: string; + url?: string; +} + +const NewsItem: React.FC = ({ title = '', time = '', url = '' }) => { + return ( +
+
url && window.open(url)} + className="flex items-center justify-between hover:text-blue-600 cursor-pointer transition duration-300 ease-in-out" + > +

{title}

+

{time}

+
+
+ ); +}; + +// 使用新闻数据渲染列表 +const NewsList: React.FC = () => { + return ( +
+
+ {/* 科技新闻 */} +
+
+

科技新闻

+ +
+
    + {mockNewsData.map((news, index) => ( + + ))} +
+
+ + {/* 社会新闻 */} +
+
+

社会新闻

+ +
+
    + {mockNewsData.map((news, index) => ( + + ))} +
+
+
+
+ ); +}; + +export default NewsList; \ No newline at end of file diff --git a/app/components/untils/Carousel.tsx b/app/components/untils/Carousel.tsx deleted file mode 100755 index 8ddf9f9..0000000 --- a/app/components/untils/Carousel.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import * as React from "react" - -import { Card, CardContent } from "@/components/ui/card" -import { - Carousel, - CarouselContent, - CarouselItem, - CarouselNext, - CarouselPrevious, -} from "@/components/ui/carousel" - -export function CarouselDemo() { - return ( - - - {Array.from({ length: 5 }).map((_, index) => ( - -
- - - {index + 1} - - -
-
- ))} -
- - -
- ) -} diff --git a/app/components/untils/Items.tsx b/app/components/untils/Items.tsx deleted file mode 100755 index e69de29..0000000 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 d3c019b..492ef22 100755 --- a/app/routes/news.tsx +++ b/app/routes/news.tsx @@ -1,8 +1,12 @@ -import { Carousel } from "@/components/ui/carousel"; +import { CarouselDemo } from "@/components/Carousel"; import type { Route } from "./+types/news"; import Integrated from "@/components/news/body/Integrated"; import CultureBgPage from "@/components/news/body/Culturebg"; -export function meta({}: Route.MetaArgs) { +import {Header} from "@/components/header/Header"; +import {TopNav} from "@/components/header/TopNav"; +import NewsList from "@/components/list/NewsList"; +import ImageGridSection from "@/components/body/ImageGridSection"; +export function meta( ) { return [ { title: "New React Router App" }, { name: "description", content: "Welcome to React Router!" }, @@ -10,9 +14,15 @@ export function meta({}: Route.MetaArgs) { } export default function Home() { + 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 diff --git a/package.json b/package.json index 53024e8..bfd31e6 100755 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dayjs": "^1.11.19", + "embla-carousel-autoplay": "^8.6.0", "embla-carousel-react": "^8.6.0", "immer": "^10.2.0", "isbot": "^5.1.31", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58cc337..d8d961f 100755 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: dayjs: specifier: ^1.11.19 version: 1.11.19 + embla-carousel-autoplay: + specifier: ^8.6.0 + version: 8.6.0(embla-carousel@8.6.0) embla-carousel-react: specifier: ^8.6.0 version: 8.6.0(react@19.2.0) @@ -1478,6 +1481,11 @@ packages: electron-to-chromium@1.5.255: resolution: {integrity: sha512-Z9oIp4HrFF/cZkDPMpz2XSuVpc1THDpT4dlmATFlJUIBVCy9Vap5/rIXsASP1CscBacBqhabwh8vLctqBwEerQ==} + embla-carousel-autoplay@8.6.0: + resolution: {integrity: sha512-OBu5G3nwaSXkZCo1A6LTaFMZ8EpkYbwIaH+bPqdBnDGQ2fh4+NbzjXjs2SktoPNKCtflfVMc75njaDHOYXcrsA==} + peerDependencies: + embla-carousel: 8.6.0 + embla-carousel-react@8.6.0: resolution: {integrity: sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==} peerDependencies: @@ -4211,6 +4219,10 @@ snapshots: electron-to-chromium@1.5.255: {} + embla-carousel-autoplay@8.6.0(embla-carousel@8.6.0): + dependencies: + embla-carousel: 8.6.0 + embla-carousel-react@8.6.0(react@19.2.0): dependencies: embla-carousel: 8.6.0 diff --git a/public/images/carousel-1.jpg b/public/images/carousel-1.jpg new file mode 100644 index 0000000..dd0a116 Binary files /dev/null and b/public/images/carousel-1.jpg differ diff --git a/public/images/carousel-2.jpg b/public/images/carousel-2.jpg new file mode 100644 index 0000000..39ff910 Binary files /dev/null and b/public/images/carousel-2.jpg differ diff --git a/public/images/carousel-3.jpg b/public/images/carousel-3.jpg new file mode 100644 index 0000000..3b4326e Binary files /dev/null and b/public/images/carousel-3.jpg differ diff --git a/public/images/carousel-4.jpg b/public/images/carousel-4.jpg new file mode 100644 index 0000000..896f77a Binary files /dev/null and b/public/images/carousel-4.jpg differ diff --git a/public/images/carousel-5.jpg b/public/images/carousel-5.jpg new file mode 100644 index 0000000..8cc7d97 Binary files /dev/null and b/public/images/carousel-5.jpg differ diff --git a/public/images/carousel-6.jpg b/public/images/carousel-6.jpg new file mode 100644 index 0000000..030543f Binary files /dev/null and b/public/images/carousel-6.jpg differ