diff --git a/apps/web/package.json b/apps/web/package.json index 76de28d..93e9ac4 100755 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -67,6 +67,7 @@ "react-resizable": "^3.0.5", "react-router-dom": "^6.24.1", "superjson": "^2.2.1", + "swiper": "^11.2.1", "tailwind-merge": "^2.6.0", "uuid": "^10.0.0", "yjs": "^13.6.20", diff --git a/apps/web/src/App.css b/apps/web/src/App.css index f309ab4..8406459 100755 --- a/apps/web/src/App.css +++ b/apps/web/src/App.css @@ -39,4 +39,27 @@ .ag-root-wrapper { border: 0px; +} + +/* styles/carousel.css */ +.swiper-button-next, +.swiper-button-prev { + @apply text-white opacity-0 transition-opacity duration-300; +} + +.swiper-button-disabled { + @apply opacity-0 cursor-not-allowed; +} + +.swiper:hover .swiper-button-next, +.swiper:hover .swiper-button-prev { + @apply opacity-70 hover:opacity-100; +} + +.swiper-pagination-bullet { + @apply bg-white/70; +} + +.swiper-pagination-bullet-active { + @apply bg-white; } \ No newline at end of file diff --git a/apps/web/src/app/main/home/page.tsx b/apps/web/src/app/main/home/page.tsx index 8633dd4..e789c32 100644 --- a/apps/web/src/app/main/home/page.tsx +++ b/apps/web/src/app/main/home/page.tsx @@ -1,10 +1,26 @@ -import GraphEditor from '@web/src/components/common/editor/graph/GraphEditor'; + +import { Carousel } from '@web/src/components/common/element/Carousel'; +import LeaderCard from '@web/src/components/common/element/LeaderCard'; import React, { useState, useCallback } from 'react'; import * as tus from 'tus-js-client'; interface TusUploadProps { onSuccess?: (response: any) => void; onError?: (error: Error) => void; } +const carouselItems = [ + { + id: 1, + image: 'https://th.bing.com/th/id/OIP.PEtRTLQwIX54HGFX5xNDYwHaE7?rs=1&pid=ImgDetMain', + title: '自然风光', + description: '壮丽的山川河流' + }, + { + id: 2, + image: 'https://eskipaper.com/images/scenery-pictures-15.jpg', + title: '城市景观', + description: '现代化的都市风貌' + } +]; const TusUploader: React.FC = ({ onSuccess, onError @@ -49,15 +65,21 @@ const TusUploader: React.FC = ({ }, [onSuccess, onError]); return ( -
-
- -
- {/*
- -
*/} - {/* */} +
+ + { diff --git a/apps/web/src/app/main/letter/list/page.tsx b/apps/web/src/app/main/letter/list/page.tsx new file mode 100644 index 0000000..f114d34 --- /dev/null +++ b/apps/web/src/app/main/letter/list/page.tsx @@ -0,0 +1,118 @@ +import { useState } from 'react'; +import { + MagnifyingGlassIcon, + StarIcon, + TrashIcon, + EnvelopeIcon, + FunnelIcon, +} from '@heroicons/react/24/outline'; +import { StarIcon as StarIconSolid } from '@heroicons/react/24/solid'; + +interface Letter { + id: string; + sender: string; + subject: string; + date: string; + priority: 'high' | 'medium' | 'low'; + isRead: boolean; + isStarred: boolean; +} + +export default function LetterListPage() { + const [letters, setLetters] = useState([ + { + id: '1', + sender: 'Gen. Charles Q. Brown Jr.', + subject: 'Strategic Force Posture Update', + date: '2024-01-22', + priority: 'high', + isRead: false, + isStarred: true, + }, + // ... existing code ... + ]); + + const [searchTerm, setSearchTerm] = useState(''); + const [selectedFilter, setSelectedFilter] = useState('all'); + + return ( +
+ {/* Header */} +
+
+ +

USAF Leadership Mailbox

+
+
+ + {/* Main Content */} +
+ {/* Search and Filter Bar */} +
+
+ + setSearchTerm(e.target.value)} + /> +
+
+ + +
+
+ + {/* Letters List */} +
+ {letters.map((letter) => ( +
+
+ +
+
+ {letter.sender} + {letter.priority === 'high' && ( + + High Priority + + )} +
+
{letter.subject}
+
+
{letter.date}
+ +
+
+ ))} +
+
+
+ ); +} diff --git a/apps/web/src/app/main/letter/searh/page.tsx b/apps/web/src/app/main/letter/searh/page.tsx new file mode 100644 index 0000000..012c1e9 --- /dev/null +++ b/apps/web/src/app/main/letter/searh/page.tsx @@ -0,0 +1,3 @@ +export default function LetterSearchPage() { + return <>Search +} \ No newline at end of file diff --git a/apps/web/src/app/main/letter/write/mock.ts b/apps/web/src/app/main/letter/write/mock.ts new file mode 100644 index 0000000..9faf55a --- /dev/null +++ b/apps/web/src/app/main/letter/write/mock.ts @@ -0,0 +1,28 @@ +import { Leader } from "./types"; + +export const leaders: Leader[] = [ + { + id: '1', + name: 'John Mitchell', + rank: 'General', + division: 'Air Combat Command', + imageUrl: 'https://th.bing.com/th/id/OIP.ea0spF2OAgI4I1KzgZFtTgHaHX?rs=1&pid=ImgDetMain', + email: 'j.mitchell@af.mil', + phone: '(555) 123-4567', + office: 'Pentagon, Wing A-123', + }, + { + id: '2', + name: 'Sarah Williams', + rank: 'Colonel', + division: 'Air Force Space Command', + imageUrl: 'https://th.bing.com/th/id/OIP.ea0spF2OAgI4I1KzgZFtTgHaHX?rs=1&pid=ImgDetMain', + }, + { + id: '3', + name: 'Michael Roberts', + rank: 'Major General', + division: 'Air Mobility Command', + imageUrl: 'https://th.bing.com/th/id/OIP.ea0spF2OAgI4I1KzgZFtTgHaHX?rs=1&pid=ImgDetMain', + }, +]; \ No newline at end of file diff --git a/apps/web/src/app/main/letter/write/page.tsx b/apps/web/src/app/main/letter/write/page.tsx new file mode 100644 index 0000000..dcef8ca --- /dev/null +++ b/apps/web/src/app/main/letter/write/page.tsx @@ -0,0 +1,200 @@ +import { useState, useMemo } from 'react'; +import { motion } from 'framer-motion'; +import { FunnelIcon, MagnifyingGlassIcon, PaperAirplaneIcon } from '@heroicons/react/24/outline'; +import { Leader } from './types'; +import { leaders } from './mock'; + + +export default function WriteLetterPage() { + const [selectedLeader, setSelectedLeader] = useState(null); + const [searchQuery, setSearchQuery] = useState(''); + const [selectedDivision, setSelectedDivision] = useState('all'); + + const divisions = useMemo(() => { + return ['all', ...new Set(leaders.map(leader => leader.division))]; + }, []); + + const filteredLeaders = useMemo(() => { + return leaders.filter(leader => { + const matchesSearch = leader.name.toLowerCase().includes(searchQuery.toLowerCase()) || + leader.rank.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesDivision = selectedDivision === 'all' || leader.division === selectedDivision; + return matchesSearch && matchesDivision; + }); + }, [searchQuery, selectedDivision]); + + return ( +
+ {/* Header Banner */} +
+
+
+ {/* 主标题 */} +
+

+ 信件投递入口 +

+

+ 保护您隐私的信件传输平台 +

+
+ + {/* 隐私保护说明 */} +
+
+ + + + 个人信息严格保密 +
+
+ + + + 支持匿名反映问题 +
+
+ + + + 网络信息加密存储 +
+
+ + {/* 隐私承诺 */} +
+

我们承诺:您的个人信息将被严格保密,不向任何第三方透露。您可以选择匿名反映问题,平台会自动过滤可能暴露身份的信息。

+
+
+
+
+ + + {/* 搜索和筛选区域 */} +
+
+
+ + setSearchQuery(e.target.value)} + /> +
+
+ + +
+
+ + {/* Modified Leader Cards Grid */} +
+ {filteredLeaders.map((leader) => ( + +
+ {/* Image Container */} +
+ {leader.name} +
+ + {/* Content Container */} +
+
+
+
+

+ {leader.name} +

+ + {leader.rank} + +
+

{leader.division}

+ + {/* Contact Information */} +
+

+ + + + {leader.email} +

+

+ + + + {leader.phone} +

+

+ + + + {leader.office} +

+
+
+ + +
+
+
+
+ ))} +
+ {/* 无结果提示 */} + {filteredLeaders.length === 0 && ( +
+

+ No leaders found matching your search criteria +

+
+ )} +
+
+ ); +} diff --git a/apps/web/src/app/main/letter/write/types.ts b/apps/web/src/app/main/letter/write/types.ts new file mode 100644 index 0000000..4d0af62 --- /dev/null +++ b/apps/web/src/app/main/letter/write/types.ts @@ -0,0 +1,12 @@ +export interface Leader { + id: string; + name: string; + rank: string; + division: string; + imageUrl: string; + email: string; // Added + phone: string; // Added + office: string; // Added +} + + diff --git a/apps/web/src/components/common/element/Carousel.tsx b/apps/web/src/components/common/element/Carousel.tsx new file mode 100644 index 0000000..c9f2c8f --- /dev/null +++ b/apps/web/src/components/common/element/Carousel.tsx @@ -0,0 +1,120 @@ +import { motion, AnimatePresence } from 'framer-motion'; +import { useEffect, useState, useRef } from 'react'; +import { Swiper, SwiperRef, SwiperSlide } from 'swiper/react'; +import { Navigation, Pagination, Autoplay } from 'swiper/modules'; +import 'swiper/css'; +import 'swiper/css/navigation'; +import 'swiper/css/pagination'; + +interface Slide { + id: number; + image: string; + title?: string; + description?: string; +} + +interface CarouselProps { + slides: Slide[]; + autoplayDelay?: number; + className?: string; + swiperConfig?: { + spaceBetween?: number; + slidesPerView?: number; + navigation?: boolean; + loop?: boolean; + pagination?: boolean | { clickable: boolean }; + autoplayOptions?: { + delay: number; + disableOnInteraction: boolean; + }; + }; +} + +export const Carousel = ({ + slides, + autoplayDelay = 3000, + className = '', + swiperConfig = {}, +}: CarouselProps) => { + const [activeIndex, setActiveIndex] = useState(0); + const swiperRef = useRef(null); + + const { + spaceBetween = 0, + slidesPerView = 1, + navigation = true, + loop = true, + pagination = { clickable: true }, + autoplayOptions = { + delay: autoplayDelay, + disableOnInteraction: false, + }, + } = swiperConfig; + + return ( +
+ setActiveIndex(swiper.realIndex)} + onSwiper={(swiper) => (swiperRef.current = swiper)} + className="w-full h-full rounded-xl" + > + {slides.map((slide) => ( + +
+ {slide.title + + {(slide.title || slide.description) && ( + + {slide.title && ( +

+ {slide.title} +

+ )} + {slide.description && ( +

+ {slide.description} +

+ )} +
+ )} +
+
+
+ ))} +
+ +
+ {slides.map((_, index) => ( +
+
+ ); +}; \ No newline at end of file diff --git a/apps/web/src/components/common/element/LeaderCard.tsx b/apps/web/src/components/common/element/LeaderCard.tsx new file mode 100644 index 0000000..0926f9d --- /dev/null +++ b/apps/web/src/components/common/element/LeaderCard.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import { Button } from "./Button"; + +type LeaderCardProps = { + name: string; + title: string; + description: string; + imageUrl: string; + + onClick?: () => void; // 添加整个卡片的点击事件 +}; + +const LeaderCard: React.FC = ({ + name, + title, + description, + imageUrl, + onClick, +}) => { + return ( +
{ + if (e.key === 'Enter' || e.key === ' ') { + onClick?.(); + } + }} + > + {/* Image Section */} +
+ {name} +
+
+ + {/* Content Section */} +
+
+ {/* Name and Title */} +
+

+ {name} +

+

+ {title} +

+
+ + {/* Description */} +

+ {description} +

+
+ + {/* Decorative Element */} +
+
+
+
+
+ ); +}; + + +export default LeaderCard; diff --git a/apps/web/src/components/common/uploader/FileUploader.tsx b/apps/web/src/components/common/uploader/FileUploader.tsx index 0d3c62c..d7e19eb 100644 --- a/apps/web/src/components/common/uploader/FileUploader.tsx +++ b/apps/web/src/components/common/uploader/FileUploader.tsx @@ -53,7 +53,7 @@ const FileItem = memo(({ file, progress, onRemove }: { )) export default function FileUploader({ - endpoint='', + endpoint = '', onSuccess, onError, maxSize = 100, diff --git a/apps/web/src/components/layout/main/Footer.tsx b/apps/web/src/components/layout/main/Footer.tsx new file mode 100644 index 0000000..f14b05e --- /dev/null +++ b/apps/web/src/components/layout/main/Footer.tsx @@ -0,0 +1,78 @@ +import { PhoneIcon } from '@heroicons/react/24/outline' + +export function Footer() { + return ( +
+
+ {/* Main Footer Content */} +
+ {/* Logo and Main Info */} +
+
+ USAF Official Emblem +
+

+ 美国空军官方领导机关信箱 +

+

+ 为美国空军提供安全可靠的通信服务 +

+
+ + {/* Technical Support */} +
+

+ 技术支持 +

+
+
+

创新高地 软件小组

+

负责系统开发维护及技术支持

+
+ + 1-800-XXX-XXXX +
+
+

+ 由软件小组提供 24/7 专业技术支持 +

+
+
+
+ + {/* Divider */} +
+ + {/* Bottom Section */} +
+
+ + 美国国防部授权网站 + + + 所有通信内容受联邦法律保护 + + + 政府信息系统 + +
+

+ © {new Date().getFullYear()} United States Air Force. All rights reserved. +

+
+
+
+ ) +} diff --git a/apps/web/src/components/layout/main/Header.tsx b/apps/web/src/components/layout/main/Header.tsx new file mode 100644 index 0000000..973b47b --- /dev/null +++ b/apps/web/src/components/layout/main/Header.tsx @@ -0,0 +1,85 @@ +import { MagnifyingGlassIcon, UserIcon } from "@heroicons/react/24/outline"; +import { Link, NavLink } from "react-router-dom"; +import { memo } from "react"; +import { NAV_ITEMS } from "./constants"; +import { SearchBar } from "./SearchBar"; +import { Logo } from "./Logo"; + +interface HeaderProps { + onSearch?: (query: string) => void; +} + +export const Header = memo(function Header({ onSearch }: HeaderProps) { + return ( +
+
+ {/* Main Content */} +
+
+ + + + {/* User Actions */} +
+ + + Login + +
+
+
+ + {/* Navigation */} + +
+
+ ); +}); diff --git a/apps/web/src/components/layout/main/Logo.tsx b/apps/web/src/components/layout/main/Logo.tsx new file mode 100644 index 0000000..2a44003 --- /dev/null +++ b/apps/web/src/components/layout/main/Logo.tsx @@ -0,0 +1,16 @@ +import { Link } from "react-router-dom"; + +export const Logo = () => ( + + USAF Logo +
+

Leadership Mailbox

+

United States Air Force

+
+ +); diff --git a/apps/web/src/components/layout/main/MainLayout.tsx b/apps/web/src/components/layout/main/MainLayout.tsx index 7134d37..03a36fa 100644 --- a/apps/web/src/components/layout/main/MainLayout.tsx +++ b/apps/web/src/components/layout/main/MainLayout.tsx @@ -1,40 +1,27 @@ -import { useState, useEffect, useRef, ReactNode } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; -import { TopNavBar } from '@web/src/components/layout/main/top-nav-bar'; -import { navItems, notificationItems } from '@web/src/components/layout/main/nav-data'; -import { Sidebar } from '@web/src/components/layout/main/side-bar'; -import { Outlet } from 'react-router-dom'; - +import { motion } from 'framer-motion' +import { Outlet } from 'react-router-dom' +import { Header } from './Header' +import { Footer } from './Footer' export function MainLayout() { - const [sidebarOpen, setSidebarOpen] = useState(true); - const [notifications, setNotifications] = useState(3); - const [recentSearches] = useState([ - 'React Fundamentals', - 'TypeScript Advanced', - 'Tailwind CSS Projects', - ]); - return ( -
- + + {/* 顶部 Header */} +
- - {sidebarOpen && } - - -
- + {/* 主要内容区域 */} +
+
+ +
-
- ); -} \ No newline at end of file + + {/* 底部 Footer */} +