|
|
@@ -1,15 +1,387 @@
|
|
|
-import React from 'react';
|
|
|
+import React, { useState, useEffect } from 'react';
|
|
|
+import { useNavigate } from 'react-router-dom';
|
|
|
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
|
+import { useElderlyUniversities } from '../hooks/useElderlyUniversityData';
|
|
|
+import ElderlyUniversityCard from '../components/ElderlyUniversityCard';
|
|
|
+import { SkeletonLoader, ListItemSkeleton } from '../components/SkeletonLoader';
|
|
|
+import {
|
|
|
+ MagnifyingGlassIcon,
|
|
|
+ MapPinIcon,
|
|
|
+ BookOpenIcon,
|
|
|
+ HeartIcon,
|
|
|
+ EyeIcon,
|
|
|
+ ArrowPathIcon
|
|
|
+} from '@heroicons/react/24/outline';
|
|
|
+
|
|
|
+// 水墨风格色彩配置
|
|
|
+const COLORS = {
|
|
|
+ ink: {
|
|
|
+ light: '#f5f3f0',
|
|
|
+ medium: '#d4c4a8',
|
|
|
+ dark: '#8b7355',
|
|
|
+ deep: '#3a2f26',
|
|
|
+ },
|
|
|
+ accent: {
|
|
|
+ red: '#a85c5c',
|
|
|
+ blue: '#4a6b7c',
|
|
|
+ green: '#5c7c5c',
|
|
|
+ },
|
|
|
+ text: {
|
|
|
+ primary: '#2f1f0f',
|
|
|
+ secondary: '#5d4e3b',
|
|
|
+ light: '#8b7355',
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const FONT_STYLES = {
|
|
|
+ title: 'font-serif text-3xl font-bold tracking-wide',
|
|
|
+ sectionTitle: 'font-serif text-2xl font-semibold tracking-wide',
|
|
|
+ body: 'font-sans text-base leading-relaxed',
|
|
|
+ caption: 'font-sans text-sm',
|
|
|
+ small: 'font-sans text-xs',
|
|
|
+};
|
|
|
+
|
|
|
+// 创建QueryClient实例
|
|
|
+const queryClient = new QueryClient({
|
|
|
+ defaultOptions: {
|
|
|
+ queries: {
|
|
|
+ staleTime: 5 * 60 * 1000,
|
|
|
+ gcTime: 10 * 60 * 1000,
|
|
|
+ refetchOnWindowFocus: false,
|
|
|
+ },
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+// 老年大学列表页面组件
|
|
|
+const ElderlyUniversityList: React.FC = () => {
|
|
|
+ const navigate = useNavigate();
|
|
|
+ const [searchQuery, setSearchQuery] = useState('');
|
|
|
+ const [isSearching, setIsSearching] = useState(false);
|
|
|
+ const [refreshKey, setRefreshKey] = useState(0);
|
|
|
+
|
|
|
+ const {
|
|
|
+ data: universities,
|
|
|
+ isLoading,
|
|
|
+ isError,
|
|
|
+ error,
|
|
|
+ refetch
|
|
|
+ } = useElderlyUniversities({
|
|
|
+ keyword: searchQuery,
|
|
|
+ page: 1,
|
|
|
+ pageSize: 10
|
|
|
+ });
|
|
|
+
|
|
|
+ // 搜索功能
|
|
|
+ const handleSearch = () => {
|
|
|
+ if (searchQuery.trim()) {
|
|
|
+ setIsSearching(true);
|
|
|
+ } else {
|
|
|
+ setIsSearching(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleKeyPress = (e: React.KeyboardEvent) => {
|
|
|
+ if (e.key === 'Enter') {
|
|
|
+ handleSearch();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleRefresh = async () => {
|
|
|
+ setRefreshKey(prev => prev + 1);
|
|
|
+ await refetch();
|
|
|
+ };
|
|
|
+
|
|
|
+ const clearSearch = () => {
|
|
|
+ setSearchQuery('');
|
|
|
+ setIsSearching(false);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleUniversityClick = (id: number) => {
|
|
|
+ navigate(`/elderly-university/${id}`);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 渲染头部统计信息
|
|
|
+ const renderStats = () => {
|
|
|
+ if (!universities?.data) return null;
|
|
|
+
|
|
|
+ const totalUniversities = universities.pagination.total;
|
|
|
+ const totalCourses = universities.data.reduce((sum, uni) => sum + uni.courseCount, 0);
|
|
|
+ const totalFavorites = universities.data.reduce((sum, uni) => sum + uni.favoriteCount, 0);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="grid grid-cols-3 gap-3 mb-6">
|
|
|
+ <div
|
|
|
+ className="text-center p-3 rounded-xl backdrop-blur-sm"
|
|
|
+ style={{
|
|
|
+ backgroundColor: 'rgba(255,255,255,0.8)',
|
|
|
+ border: `1px solid ${COLORS.ink.medium}`
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ className="text-2xl font-bold mb-1"
|
|
|
+ style={{ color: COLORS.accent.blue }}
|
|
|
+ >
|
|
|
+ {totalUniversities}
|
|
|
+ </div>
|
|
|
+ <div className={FONT_STYLES.caption} style={{ color: COLORS.text.secondary }}>
|
|
|
+ 合作院校
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ className="text-center p-3 rounded-xl backdrop-blur-sm"
|
|
|
+ style={{
|
|
|
+ backgroundColor: 'rgba(255,255,255,0.8)',
|
|
|
+ border: `1px solid ${COLORS.ink.medium}`
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ className="text-2xl font-bold mb-1"
|
|
|
+ style={{ color: COLORS.accent.green }}
|
|
|
+ >
|
|
|
+ {totalCourses}
|
|
|
+ </div>
|
|
|
+ <div className={FONT_STYLES.caption} style={{ color: COLORS.text.secondary }}>
|
|
|
+ 精品课程
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ className="text-center p-3 rounded-xl backdrop-blur-sm"
|
|
|
+ style={{
|
|
|
+ backgroundColor: 'rgba(255,255,255,0.8)',
|
|
|
+ border: `1px solid ${COLORS.ink.medium}`
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ className="text-2xl font-bold mb-1"
|
|
|
+ style={{ color: COLORS.accent.red }}
|
|
|
+ >
|
|
|
+ {totalFavorites}
|
|
|
+ </div>
|
|
|
+ <div className={FONT_STYLES.caption} style={{ color: COLORS.text.secondary }}>
|
|
|
+ 学员关注
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ // 加载状态
|
|
|
+ if (isLoading) {
|
|
|
+ return (
|
|
|
+ <div className="min-h-screen" style={{ backgroundColor: COLORS.ink.light }}>
|
|
|
+ <HeaderSkeleton />
|
|
|
+ <div className="p-4 space-y-4">
|
|
|
+ <div className="px-4 py-3">
|
|
|
+ <div className="h-8 rounded" style={{ backgroundColor: COLORS.ink.medium, width: '8rem' }}></div>
|
|
|
+ </div>
|
|
|
+ <ListItemSkeleton count={5} />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 错误状态
|
|
|
+ if (isError) {
|
|
|
+ return (
|
|
|
+ <div className="min-h-screen flex items-center justify-center" style={{ backgroundColor: COLORS.ink.light }}>
|
|
|
+ <div className="text-center p-8">
|
|
|
+ <div className="text-6xl mb-4">📚</div>
|
|
|
+ <h3 className={`${FONT_STYLES.sectionTitle} mb-2`} style={{ color: COLORS.text.primary }}>
|
|
|
+ 加载失败
|
|
|
+ </h3>
|
|
|
+ <p className={FONT_STYLES.caption} style={{ color: COLORS.text.secondary }}>
|
|
|
+ {(error as Error)?.message || '获取老年大学信息失败,请稍后重试'}
|
|
|
+ </p>
|
|
|
+ <button
|
|
|
+ onClick={handleRefresh}
|
|
|
+ className="mt-4 px-4 py-2 rounded-full border transition-all duration-300 hover:shadow-md"
|
|
|
+ style={{
|
|
|
+ color: COLORS.text.primary,
|
|
|
+ borderColor: COLORS.ink.medium,
|
|
|
+ backgroundColor: 'transparent'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 重新加载
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 空状态
|
|
|
+ if (!universities?.data || universities.data.length === 0) {
|
|
|
+ return (
|
|
|
+ <div className="min-h-screen flex items-center justify-center" style={{ backgroundColor: COLORS.ink.light }}>
|
|
|
+ <div className="text-center p-8">
|
|
|
+ <div className="text-6xl mb-4">🏫</div>
|
|
|
+ <h3 className={`${FONT_STYLES.sectionTitle} mb-2`} style={{ color: COLORS.text.primary }}>
|
|
|
+ {isSearching ? '搜索无结果' : '暂无老年大学信息'}
|
|
|
+ </h3>
|
|
|
+ <p className={FONT_STYLES.caption} style={{ color: COLORS.text.secondary }}>
|
|
|
+ {isSearching
|
|
|
+ ? '换个关键词试试,或查看全部老年大学'
|
|
|
+ : '我们正在努力为您搜集更多优质老年大学资源'}
|
|
|
+ </p>
|
|
|
+ {isSearching && (
|
|
|
+ <button
|
|
|
+ onClick={clearSearch}
|
|
|
+ className="mt-4 px-4 py-2 rounded-full border transition-all duration-300 hover:shadow-md"
|
|
|
+ style={{
|
|
|
+ color: COLORS.text.primary,
|
|
|
+ borderColor: COLORS.ink.medium,
|
|
|
+ backgroundColor: 'transparent'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 查看全部
|
|
|
+ </button>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
-const ElderlyUniversityPage = () => {
|
|
|
return (
|
|
|
- <div className="min-h-screen bg-gray-50">
|
|
|
- <header className="bg-blue-600 text-white p-4">
|
|
|
- <h1 className="text-xl font-bold">老年大学</h1>
|
|
|
+ <div className="min-h-screen" style={{ backgroundColor: COLORS.ink.light }}>
|
|
|
+ {/* 头部导航栏 */}
|
|
|
+ <header
|
|
|
+ className="shadow-sm sticky top-0 z-10 border-b border-opacity-20"
|
|
|
+ style={{
|
|
|
+ backgroundColor: COLORS.ink.light,
|
|
|
+ borderColor: COLORS.ink.medium
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div className="px-4 py-4">
|
|
|
+ <h1
|
|
|
+ className={`${FONT_STYLES.title} mb-4`}
|
|
|
+ style={{ color: COLORS.text.primary }}
|
|
|
+ >
|
|
|
+ 老年大学
|
|
|
+ </h1>
|
|
|
+
|
|
|
+ {/* 搜索栏 */}
|
|
|
+ <div className="flex items-center rounded-full px-4 py-3 shadow-sm border transition-all duration-300 focus-within:shadow-md"
|
|
|
+ style={{
|
|
|
+ backgroundColor: 'rgba(255,255,255,0.7)',
|
|
|
+ borderColor: COLORS.ink.medium
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <MagnifyingGlassIcon
|
|
|
+ className="w-5 h-5 mr-2"
|
|
|
+ style={{ color: COLORS.ink.dark }}
|
|
|
+ />
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ placeholder="搜索学校名称、课程或地址..."
|
|
|
+ value={searchQuery}
|
|
|
+ onChange={(e) => setSearchQuery(e.target.value)}
|
|
|
+ onKeyPress={handleKeyPress}
|
|
|
+ className={`flex-1 bg-transparent outline-none ${FONT_STYLES.body}`}
|
|
|
+ style={{ color: COLORS.text.primary, fontSize: '16px' }}
|
|
|
+ />
|
|
|
+ {searchQuery && (
|
|
|
+ <button
|
|
|
+ onClick={clearSearch}
|
|
|
+ className={`${FONT_STYLES.caption} px-2 py-1 ml-2`}
|
|
|
+ style={{ color: COLORS.text.light }}
|
|
|
+ >
|
|
|
+ 清除
|
|
|
+ </button>
|
|
|
+ )}
|
|
|
+ <button
|
|
|
+ onClick={handleSearch}
|
|
|
+ className={`${FONT_STYLES.caption} px-3 py-1 rounded-full transition-colors`}
|
|
|
+ style={{
|
|
|
+ color: COLORS.text.primary,
|
|
|
+ backgroundColor: COLORS.ink.medium
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 搜索
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</header>
|
|
|
- <div className="p-4">
|
|
|
- <p className="text-center text-gray-500 py-8">老年大学页面开发中...</p>
|
|
|
+
|
|
|
+ {/* 主要内容 */}
|
|
|
+ <div className="pb-16">
|
|
|
+ <div className="p-4">
|
|
|
+ {/* 统计信息 */}
|
|
|
+ {renderStats()}
|
|
|
+
|
|
|
+ {/* 列表头部 */}
|
|
|
+ <div className="flex justify-between items-center mb-4">
|
|
|
+ <h2
|
|
|
+ className={`${FONT_STYLES.sectionTitle}`}
|
|
|
+ style={{ color: COLORS.text.primary }}
|
|
|
+ >
|
|
|
+ {isSearching ? '搜索结果' : '推荐院校'}
|
|
|
+ </h2>
|
|
|
+ <button
|
|
|
+ onClick={handleRefresh}
|
|
|
+ className={`${FONT_STYLES.caption} px-3 py-1 rounded-full border transition-all duration-300 hover:shadow-md`}
|
|
|
+ style={{
|
|
|
+ color: COLORS.text.primary,
|
|
|
+ borderColor: COLORS.ink.medium,
|
|
|
+ backgroundColor: 'transparent'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <ArrowPathIcon className="w-4 h-4" />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 学校列表 */}
|
|
|
+ <div className="space-y-4">
|
|
|
+ {universities.data.map((university) => (
|
|
|
+ <ElderlyUniversityCard
|
|
|
+ key={university.id}
|
|
|
+ university={university}
|
|
|
+ onClick={() => handleUniversityClick(university.id)}
|
|
|
+ />
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 加载更多提示 */}
|
|
|
+ {universities.pagination.total > universities.data.length && (
|
|
|
+ <div className="text-center mt-6">
|
|
|
+ <p className={FONT_STYLES.caption} style={{ color: COLORS.text.secondary }}>
|
|
|
+ 共 {universities.pagination.total} 所院校,已显示 {universities.data.length} 所
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+
|
|
|
+ {/* 刷新按钮 */}
|
|
|
+ <button
|
|
|
+ onClick={handleRefresh}
|
|
|
+ className="fixed bottom-20 right-4 p-3 rounded-full shadow-lg transition-all duration-300 hover:shadow-xl"
|
|
|
+ style={{
|
|
|
+ backgroundColor: COLORS.ink.dark,
|
|
|
+ color: 'white'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <ArrowPathIcon className="w-5 h-5" />
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+// 水墨风格骨架屏
|
|
|
+const HeaderSkeleton: React.FC = () => (
|
|
|
+ <div className="px-4 py-4" style={{ backgroundColor: COLORS.ink.light }}>
|
|
|
+ <div className="h-8 rounded mb-4" style={{ backgroundColor: COLORS.ink.medium, width: '10rem' }}></div>
|
|
|
+ <div className="flex items-center rounded-full px-4 py-3" style={{ backgroundColor: COLORS.ink.light, border: `1px solid ${COLORS.ink.medium}` }}>
|
|
|
+ <div className="w-5 h-5 rounded mr-2" style={{ backgroundColor: COLORS.ink.medium }}></div>
|
|
|
+ <div className="h-4 rounded flex-1" style={{ backgroundColor: COLORS.ink.medium }}></div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
+);
|
|
|
+
|
|
|
+// 主页面组件包装
|
|
|
+const ElderlyUniversityPage: React.FC = () => {
|
|
|
+ return (
|
|
|
+ <QueryClientProvider client={queryClient}>
|
|
|
+ <ElderlyUniversityList />
|
|
|
+ </QueryClientProvider>
|
|
|
);
|
|
|
};
|
|
|
|