Merge branch 'main' of http://113.45.67.59:3003/qiuchenfan/news
This commit is contained in:
commit
114c63d6d7
|
|
@ -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 (
|
||||||
|
<div
|
||||||
|
className="relative w-[1280px] h-[704px] bg-cover bg-center left-1/2 transform -translate-x-1/2"
|
||||||
|
style={{ backgroundImage: "url('/app/images/header.png')" }}
|
||||||
|
>
|
||||||
|
{/* 时间显示 */}
|
||||||
|
<div className="absolute top-4 right-4 mr-40">
|
||||||
|
<div>
|
||||||
|
{currentTime.toLocaleString('zh-CN')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute top-4 right-4 mr-20 cursor-pointer">
|
||||||
|
<h2 className="text-lg font-bold" onClick={() => console.log('登录')}>登录</h2>
|
||||||
|
</div>
|
||||||
|
<div className="absolute top-4 right-4 mr-5">
|
||||||
|
<h2 className="text-lg font-bold cursor-pointer" onClick={() => console.log('注册')}>注册</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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 (
|
||||||
|
<div className="h-14 flex items-center justify-center px-8 bg-white">
|
||||||
|
{/* 搜索框与导航菜单组合 */}
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
{/* 搜索框 */}
|
||||||
|
<form onSubmit={handleSearchSubmit} className="relative">
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchKeyword}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
<span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400">
|
||||||
|
<Search className="w-5 h-5" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* 导航菜单 */}
|
||||||
|
<ul className="flex space-x-2">
|
||||||
|
{menuItems.map((item) => {
|
||||||
|
const isActive = currentActiveKey === item.key; // 判断当前项是否激活
|
||||||
|
return (
|
||||||
|
<li key={item.key}>
|
||||||
|
<button
|
||||||
|
onClick={() => handleItemClick(item)}
|
||||||
|
className={`px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200 ${
|
||||||
|
isActive
|
||||||
|
? 'bg-blue-600 text-white shadow-md' // 激活状态样式
|
||||||
|
: 'text-gray-600 hover:bg-blue-100 hover:text-blue-700' // 非激活状态样式
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import Autoplay from "embla-carousel-autoplay";
|
import Autoplay from "embla-carousel-autoplay";
|
||||||
|
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/ui/card";
|
||||||
import {
|
import {
|
||||||
Carousel,
|
Carousel,
|
||||||
CarouselContent,
|
CarouselContent,
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
CarouselNext,
|
CarouselNext,
|
||||||
CarouselPrevious,
|
CarouselPrevious,
|
||||||
type CarouselApi,
|
type CarouselApi,
|
||||||
} from "@/components/ui/carousel";
|
} from "@/ui/carousel";
|
||||||
|
|
||||||
export function CarouselDemo() {
|
export function CarouselDemo() {
|
||||||
const [api, setApi] = React.useState<CarouselApi>();
|
const [api, setApi] = React.useState<CarouselApi>();
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 471 KiB |
|
|
@ -1,5 +1,5 @@
|
||||||
import { CarouselDemo } from "@/components/untils/Carousel";
|
import { CarouselDemo } from "@/components/untils/Carousel";
|
||||||
import NewsList from "@/components/list/NewsList";
|
import type { Route } from "./+types/news";
|
||||||
|
|
||||||
|
|
||||||
export function meta( ) {
|
export function meta( ) {
|
||||||
|
|
@ -10,5 +10,5 @@ export function meta( ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return <NewsList />;
|
return <CarouselDemo />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import useEmblaCarousel, {
|
||||||
import { ArrowLeft, ArrowRight } from "lucide-react"
|
import { ArrowLeft, ArrowRight } from "lucide-react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/ui/button"
|
||||||
|
|
||||||
type CarouselApi = UseEmblaCarouselType[1]
|
type CarouselApi = UseEmblaCarouselType[1]
|
||||||
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
||||||
Loading…
Reference in New Issue