|
|
@@ -1,10 +1,95 @@
|
|
|
-import React, { useState, useRef, useCallback } from 'react';
|
|
|
+import React, { useState, useEffect, useRef } from 'react';
|
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
import { useHomeData } from '../hooks/useHomeData';
|
|
|
-import { EnhancedCarousel } from '../components/EnhancedCarousel';
|
|
|
-import { UserStatsCard } from '../components/UserStatsCard';
|
|
|
-import { SkeletonLoader, BannerSkeleton, ListItemSkeleton, UserStatsSkeleton } from '../components/SkeletonLoader';
|
|
|
import { useAuth } from '../hooks/AuthProvider';
|
|
|
+import { EnhancedCarousel } from '../components/EnhancedCarousel';
|
|
|
+import { AdBannerCarousel } from '../components/AdBannerCarousel';
|
|
|
+import { SkeletonLoader, BannerSkeleton, ListItemSkeleton } from '../components/SkeletonLoader';
|
|
|
+import {
|
|
|
+ BriefcaseIcon,
|
|
|
+ UserGroupIcon,
|
|
|
+ BookOpenIcon,
|
|
|
+ AcademicCapIcon,
|
|
|
+ ClockIcon,
|
|
|
+ NewspaperIcon,
|
|
|
+ MagnifyingGlassIcon,
|
|
|
+ ArrowPathIcon,
|
|
|
+ UserIcon,
|
|
|
+ ArrowRightOnRectangleIcon,
|
|
|
+ MapPinIcon,
|
|
|
+ EyeIcon,
|
|
|
+ CalendarDaysIcon,
|
|
|
+ TrophyIcon
|
|
|
+} 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',
|
|
|
+};
|
|
|
+
|
|
|
+// 服务分类配置
|
|
|
+const serviceCategories = [
|
|
|
+ {
|
|
|
+ name: '银龄岗位',
|
|
|
+ icon: BriefcaseIcon,
|
|
|
+ path: '/silver-jobs',
|
|
|
+ color: COLORS.accent.blue
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '银龄人才',
|
|
|
+ icon: UserGroupIcon,
|
|
|
+ path: '/silver-talents',
|
|
|
+ color: COLORS.accent.green
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '银龄智库',
|
|
|
+ icon: BookOpenIcon,
|
|
|
+ path: '/silver-wisdom',
|
|
|
+ color: COLORS.accent.red
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '老年大学',
|
|
|
+ icon: AcademicCapIcon,
|
|
|
+ path: '/elderly-university',
|
|
|
+ color: COLORS.ink.dark
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '时间银行',
|
|
|
+ icon: ClockIcon,
|
|
|
+ path: '/time-bank',
|
|
|
+ color: '#7c5c4a'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '政策资讯',
|
|
|
+ icon: NewspaperIcon,
|
|
|
+ path: '/policy-news',
|
|
|
+ color: COLORS.ink.deep
|
|
|
+ }
|
|
|
+];
|
|
|
|
|
|
// 数据转换工具
|
|
|
const transformPolicyNews = (news: any[]) =>
|
|
|
@@ -24,7 +109,8 @@ const transformJobs = (jobs: any[]) =>
|
|
|
company: job.company?.name || '未知公司',
|
|
|
salary: job.salaryRange || '面议',
|
|
|
image: job.company?.logo || `https://picsum.photos/seed/${job.id}/200/200`,
|
|
|
- tags: [job.location ? job.location.split(' ')[0] : '全国', '热门']
|
|
|
+ tags: [job.location ? job.location.split(' ')[0] : '全国', '热门'],
|
|
|
+ createdAt: job.createdAt
|
|
|
}));
|
|
|
|
|
|
const transformKnowledge = (knowledge: any[]) =>
|
|
|
@@ -33,16 +119,28 @@ const transformKnowledge = (knowledge: any[]) =>
|
|
|
title: item.title,
|
|
|
category: item.category?.name || '其他',
|
|
|
coverImage: item.coverImage || `https://picsum.photos/seed/${item.id}/200/200`,
|
|
|
- viewCount: item.viewCount || 0
|
|
|
+ viewCount: item.viewCount || 0,
|
|
|
+ createdAt: item.createdAt
|
|
|
+ }));
|
|
|
+
|
|
|
+const transformTimeBank = (activities: any[]) =>
|
|
|
+ activities.map(activity => ({
|
|
|
+ id: activity.id,
|
|
|
+ workType: activity.workType === 1 ? '志愿服务' : '技能培训',
|
|
|
+ organization: activity.organization,
|
|
|
+ workHours: activity.workHours,
|
|
|
+ earnedPoints: activity.earnedPoints,
|
|
|
+ workDate: new Date(activity.workDate).toLocaleDateString()
|
|
|
}));
|
|
|
|
|
|
+// 主页面组件
|
|
|
const NewHomePage: React.FC = () => {
|
|
|
const navigate = useNavigate();
|
|
|
const { user } = useAuth();
|
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
|
const searchRef = useRef<HTMLInputElement>(null);
|
|
|
+ const [isPulling, setIsPulling] = useState(false);
|
|
|
|
|
|
- // 获取首页数据
|
|
|
const {
|
|
|
data: homeData,
|
|
|
isLoading,
|
|
|
@@ -51,12 +149,22 @@ const NewHomePage: React.FC = () => {
|
|
|
refetch
|
|
|
} = useHomeData();
|
|
|
|
|
|
- // 处理搜索
|
|
|
- const handleSearch = useCallback(() => {
|
|
|
+ // 下拉刷新处理
|
|
|
+ const handlePullToRefresh = async () => {
|
|
|
+ setIsPulling(true);
|
|
|
+ try {
|
|
|
+ await refetch();
|
|
|
+ } finally {
|
|
|
+ setIsPulling(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 搜索处理
|
|
|
+ const handleSearch = () => {
|
|
|
if (searchQuery.trim()) {
|
|
|
navigate(`/search?q=${encodeURIComponent(searchQuery)}`);
|
|
|
}
|
|
|
- }, [searchQuery, navigate]);
|
|
|
+ };
|
|
|
|
|
|
const handleKeyPress = (e: React.KeyboardEvent) => {
|
|
|
if (e.key === 'Enter') {
|
|
|
@@ -64,94 +172,118 @@ const NewHomePage: React.FC = () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- // 下拉刷新
|
|
|
- const handlePullToRefresh = useCallback(() => {
|
|
|
- refetch();
|
|
|
- }, [refetch]);
|
|
|
-
|
|
|
- // 错误状态处理
|
|
|
- if (isError) {
|
|
|
- return (
|
|
|
- <div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
|
|
|
- <div className="text-center">
|
|
|
- <div className="text-red-500 mb-4">加载失败</div>
|
|
|
- <div className="text-gray-600 mb-4">{error?.message || '获取首页数据失败'}</div>
|
|
|
- <button
|
|
|
- onClick={() => refetch()}
|
|
|
- className="bg-blue-600 text-white px-4 py-2 rounded-lg"
|
|
|
- >
|
|
|
- 重新加载
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- // 骨架屏加载状态
|
|
|
+ // 加载状态
|
|
|
if (isLoading || !homeData) {
|
|
|
return (
|
|
|
- <div className="min-h-screen bg-gray-50">
|
|
|
- <div className="sticky top-0 z-10 bg-white shadow-sm">
|
|
|
- <div className="px-4 py-3">
|
|
|
- <div className="flex items-center justify-between mb-3">
|
|
|
- <div className="h-6 bg-gray-200 rounded w-32 animate-pulse"></div>
|
|
|
- <div className="h-8 bg-gray-200 rounded px-4 animate-pulse"></div>
|
|
|
- </div>
|
|
|
- <div className="flex items-center bg-gray-100 rounded-full px-4 py-2">
|
|
|
- <div className="w-5 h-5 bg-gray-300 rounded mr-2 animate-pulse"></div>
|
|
|
- <div className="h-4 bg-gray-200 rounded flex-1 animate-pulse"></div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
+ <div className="min-h-screen" style={{ backgroundColor: COLORS.ink.light }}>
|
|
|
+ <HeaderSkeleton />
|
|
|
<div className="p-4 space-y-4">
|
|
|
<BannerSkeleton />
|
|
|
- <UserStatsSkeleton />
|
|
|
- <ListItemSkeleton count={3} />
|
|
|
- <ListItemSkeleton count={3} />
|
|
|
<ListItemSkeleton count={3} />
|
|
|
</div>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- // 数据转换
|
|
|
+ // 数据转换与排序
|
|
|
const banners = transformPolicyNews(homeData.banners || []);
|
|
|
- const recommendedJobs = transformJobs(homeData.recommendedJobs || []);
|
|
|
- const hotKnowledge = transformKnowledge(homeData.hotKnowledge || []);
|
|
|
- const timeBankActivities = homeData.timeBankActivities || [];
|
|
|
+ const recommendedJobs = transformJobs(homeData.recommendedJobs || [])
|
|
|
+ .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
|
|
+ .slice(0, 3);
|
|
|
+ const hotKnowledge = transformKnowledge(homeData.hotKnowledge || [])
|
|
|
+ .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
|
|
+ .slice(0, 3);
|
|
|
+
|
|
|
+ // 模拟银龄库数据
|
|
|
+ const silverTalents = [
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ name: '李老先生',
|
|
|
+ specialty: '书法大师',
|
|
|
+ city: '北京',
|
|
|
+ createdAt: '2024-07-15',
|
|
|
+ avatar: '/images/elderly-avatar1.jpg'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2,
|
|
|
+ name: '王奶奶',
|
|
|
+ specialty: '园艺专家',
|
|
|
+ city: '上海',
|
|
|
+ createdAt: '2024-07-14',
|
|
|
+ avatar: '/images/elderly-avatar2.jpg'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3,
|
|
|
+ name: '张师傅',
|
|
|
+ specialty: '中医养生',
|
|
|
+ city: '杭州',
|
|
|
+ createdAt: '2024-07-13',
|
|
|
+ avatar: '/images/elderly-avatar3.jpg'
|
|
|
+ }
|
|
|
+ ];
|
|
|
+
|
|
|
+ const timeBankActivities = transformTimeBank(homeData.timeBankActivities || []);
|
|
|
|
|
|
return (
|
|
|
- <div className="min-h-screen bg-gray-50">
|
|
|
+ <div className="min-h-screen" style={{ backgroundColor: COLORS.ink.light }}>
|
|
|
{/* 顶部导航栏 */}
|
|
|
- <header className="bg-white shadow-sm sticky top-0 z-10">
|
|
|
- <div className="px-4 py-3">
|
|
|
+ <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">
|
|
|
<div className="flex items-center justify-between mb-3">
|
|
|
- <h1 className="text-xl font-bold text-blue-600">银龄智慧平台</h1>
|
|
|
+ <h1
|
|
|
+ className={`${FONT_STYLES.title}`}
|
|
|
+ style={{ color: COLORS.text.primary }}
|
|
|
+ >
|
|
|
+ 银龄智慧
|
|
|
+ </h1>
|
|
|
{user ? (
|
|
|
<div className="flex items-center space-x-2">
|
|
|
- <span className="text-sm text-gray-600">欢迎,{user.username}</span>
|
|
|
+ <span
|
|
|
+ className={`${FONT_STYLES.caption}`}
|
|
|
+ style={{ color: COLORS.text.secondary }}
|
|
|
+ >
|
|
|
+ 欢迎,{user.username}
|
|
|
+ </span>
|
|
|
<img
|
|
|
src={user.avatar || '/images/avatar-placeholder.jpg'}
|
|
|
alt="用户头像"
|
|
|
- className="w-8 h-8 rounded-full"
|
|
|
+ className="w-8 h-8 rounded-full object-cover border-2"
|
|
|
+ style={{ borderColor: COLORS.ink.medium }}
|
|
|
/>
|
|
|
</div>
|
|
|
) : (
|
|
|
<button
|
|
|
onClick={() => navigate('/login')}
|
|
|
- className="text-blue-600 text-sm px-3 py-1 rounded-full border border-blue-600"
|
|
|
+ className={`${FONT_STYLES.caption} px-3 py-1 rounded-full border transition-all duration-300 hover:shadow-md flex items-center space-x-1`}
|
|
|
+ style={{
|
|
|
+ color: COLORS.text.primary,
|
|
|
+ borderColor: COLORS.ink.medium,
|
|
|
+ backgroundColor: 'transparent'
|
|
|
+ }}
|
|
|
>
|
|
|
- 登录
|
|
|
+ <ArrowRightOnRectangleIcon className="w-4 h-4" />
|
|
|
+ <span>登录</span>
|
|
|
</button>
|
|
|
)}
|
|
|
</div>
|
|
|
|
|
|
{/* 搜索栏 */}
|
|
|
- <div className="flex items-center bg-gray-100 rounded-full px-4 py-2">
|
|
|
- <svg className="w-5 h-5 text-gray-400 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
|
- </svg>
|
|
|
+ <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
|
|
|
ref={searchRef}
|
|
|
type="text"
|
|
|
@@ -159,11 +291,16 @@ const NewHomePage: React.FC = () => {
|
|
|
value={searchQuery}
|
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
|
onKeyPress={handleKeyPress}
|
|
|
- className="flex-1 bg-transparent outline-none text-sm"
|
|
|
+ className={`flex-1 bg-transparent outline-none ${FONT_STYLES.body}`}
|
|
|
+ style={{ color: COLORS.text.primary, fontSize: '16px' }}
|
|
|
/>
|
|
|
<button
|
|
|
onClick={handleSearch}
|
|
|
- className="text-blue-600 text-xs px-2"
|
|
|
+ className={`${FONT_STYLES.caption} px-3 py-1 rounded-full transition-colors`}
|
|
|
+ style={{
|
|
|
+ color: COLORS.text.primary,
|
|
|
+ backgroundColor: COLORS.ink.medium
|
|
|
+ }}
|
|
|
>
|
|
|
搜索
|
|
|
</button>
|
|
|
@@ -171,135 +308,315 @@ const NewHomePage: React.FC = () => {
|
|
|
</div>
|
|
|
</header>
|
|
|
|
|
|
- {/* 下拉刷新区域 */}
|
|
|
- <div className="relative">
|
|
|
- {/* 轮播图 */}
|
|
|
- <div className="bg-white">
|
|
|
- <EnhancedCarousel
|
|
|
- items={banners}
|
|
|
- autoPlayInterval={5000}
|
|
|
- />
|
|
|
+ {/* 下拉刷新指示器 */}
|
|
|
+ {isPulling && (
|
|
|
+ <div className="text-center py-2"
|
|
|
+ style={{ backgroundColor: COLORS.ink.medium }}
|
|
|
+ >
|
|
|
+ <div className="flex items-center justify-center text-white">
|
|
|
+ <ArrowPathIcon className="w-4 h-4 animate-spin mr-2" />
|
|
|
+ 正在刷新...
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ )}
|
|
|
|
|
|
- {/* 用户统计卡片 */}
|
|
|
- {user && (
|
|
|
- <UserStatsCard stats={homeData.userStats} />
|
|
|
+ {/* 主要内容 */}
|
|
|
+ <div className="pb-16">
|
|
|
+ {/* 轮播图 */}
|
|
|
+ {banners.length > 0 && (
|
|
|
+ <div className="bg-white bg-opacity-60 backdrop-blur-sm">
|
|
|
+ <EnhancedCarousel
|
|
|
+ items={banners}
|
|
|
+ autoPlayInterval={5000}
|
|
|
+ className="rounded-b-2xl"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
)}
|
|
|
|
|
|
+ {/* 广告位轮播 */}
|
|
|
+ <AdBannerCarousel />
|
|
|
+
|
|
|
{/* 服务分类 */}
|
|
|
- <div className="bg-white mt-2 p-4">
|
|
|
- <div className="grid grid-cols-3 gap-4">
|
|
|
- {[
|
|
|
- { name: '银龄岗', icon: '💼', path: '/silver-jobs', color: 'bg-blue-500' },
|
|
|
- { name: '银龄库', icon: '👥', path: '/silver-talents', color: 'bg-green-500' },
|
|
|
- { name: '银龄智库', icon: '📚', path: '/silver-wisdom', color: 'bg-purple-500' },
|
|
|
- { name: '老年大学', icon: '🎓', path: '/elderly-university', color: 'bg-orange-500' },
|
|
|
- { name: '时间银行', icon: '⏰', path: '/time-bank', color: 'bg-red-500' },
|
|
|
- { name: '政策资讯', icon: '📰', path: '/policy-news', color: 'bg-indigo-500' }
|
|
|
- ].map((category, index) => (
|
|
|
+ <div className="mt-4 px-4">
|
|
|
+ <div className="grid grid-cols-3 gap-3">
|
|
|
+ {serviceCategories.map((category, index) => (
|
|
|
<button
|
|
|
key={index}
|
|
|
onClick={() => navigate(category.path)}
|
|
|
- className="flex flex-col items-center p-3 rounded-lg hover:bg-gray-50 transition-colors"
|
|
|
+ className="flex flex-col items-center p-4 rounded-xl transition-all duration-300 hover:shadow-lg backdrop-blur-sm"
|
|
|
+ style={{
|
|
|
+ backgroundColor: 'rgba(255,255,255,0.7)',
|
|
|
+ border: `1px solid ${COLORS.ink.medium}`,
|
|
|
+ color: COLORS.text.primary
|
|
|
+ }}
|
|
|
>
|
|
|
- <div className={`${category.color} text-white w-12 h-12 rounded-full flex items-center justify-center text-xl mb-2`}>
|
|
|
- {category.icon}
|
|
|
+ <div
|
|
|
+ className="w-14 h-14 rounded-full flex items-center justify-center shadow-sm"
|
|
|
+ style={{ backgroundColor: category.color, color: 'white' }}
|
|
|
+ >
|
|
|
+ <category.icon className="w-6 h-6" />
|
|
|
</div>
|
|
|
- <span className="text-sm text-gray-700">{category.name}</span>
|
|
|
+ <span className={`${FONT_STYLES.caption} mt-2`}>{category.name}</span>
|
|
|
</button>
|
|
|
))}
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
{/* 推荐岗位 */}
|
|
|
- <div className="bg-white mt-2 p-4">
|
|
|
- <div className="flex justify-between items-center mb-4">
|
|
|
- <h2 className="text-lg font-bold text-gray-900">推荐岗位</h2>
|
|
|
- <button
|
|
|
- onClick={() => navigate('/silver-jobs')}
|
|
|
- className="text-blue-600 text-sm"
|
|
|
- >
|
|
|
- 查看更多 →
|
|
|
- </button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="space-y-3">
|
|
|
- {recommendedJobs.slice(0, 3).map((item) => (
|
|
|
- <div
|
|
|
- key={item.id}
|
|
|
- className="flex items-center p-3 bg-gray-50 rounded-lg cursor-pointer hover:bg-gray-100 transition-colors"
|
|
|
- onClick={() => navigate(`/silver-jobs/${item.id}`)}
|
|
|
+ {recommendedJobs.length > 0 && (
|
|
|
+ <div className="mt-6 px-4">
|
|
|
+ <div className="flex justify-between items-center mb-4">
|
|
|
+ <h2
|
|
|
+ className={`${FONT_STYLES.sectionTitle}`}
|
|
|
+ style={{ color: COLORS.text.primary }}
|
|
|
>
|
|
|
- <img
|
|
|
- src={item.image}
|
|
|
- alt={item.title}
|
|
|
- className="w-12 h-12 rounded-full object-cover mr-3"
|
|
|
- loading="lazy"
|
|
|
- />
|
|
|
- <div className="flex-1">
|
|
|
- <h4 className="font-medium text-gray-900 line-clamp-1">{item.title}</h4>
|
|
|
- <p className="text-sm text-gray-600 line-clamp-1">{item.company}</p>
|
|
|
- <div className="flex items-center mt-1">
|
|
|
- <span className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">
|
|
|
- {item.salary}
|
|
|
- </span>
|
|
|
- {item.tags.slice(0, 1).map((tag, i) => (
|
|
|
- <span key={i} className="text-xs bg-green-100 text-green-800 px-2 py-1 rounded ml-2">
|
|
|
- {tag}
|
|
|
+ 推荐岗位
|
|
|
+ </h2>
|
|
|
+ <button
|
|
|
+ onClick={() => navigate('/silver-jobs')}
|
|
|
+ className={`${FONT_STYLES.caption} px-4 py-2 rounded-full border transition-all duration-300 hover:shadow-md flex items-center space-x-1`}
|
|
|
+ style={{
|
|
|
+ color: COLORS.text.primary,
|
|
|
+ borderColor: COLORS.ink.medium,
|
|
|
+ backgroundColor: 'transparent'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <span>显示更多</span>
|
|
|
+ <span>→</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="space-y-3">
|
|
|
+ {recommendedJobs.map((item) => (
|
|
|
+ <div
|
|
|
+ key={item.id}
|
|
|
+ className="flex items-center p-4 rounded-xl shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer backdrop-blur-sm"
|
|
|
+ style={{
|
|
|
+ backgroundColor: 'rgba(255,255,255,0.8)',
|
|
|
+ border: `1px solid ${COLORS.ink.medium}`
|
|
|
+ }}
|
|
|
+ onClick={() => navigate(`/silver-jobs/${item.id}`)}
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ src={item.image}
|
|
|
+ alt={item.title}
|
|
|
+ className="w-14 h-14 rounded-full object-cover mr-4 border-2"
|
|
|
+ style={{ borderColor: COLORS.ink.medium }}
|
|
|
+ />
|
|
|
+ <div className="flex-1">
|
|
|
+ <h4
|
|
|
+ className={`font-medium ${FONT_STYLES.body}`}
|
|
|
+ style={{ color: COLORS.text.primary }}
|
|
|
+ >
|
|
|
+ {item.title}
|
|
|
+ </h4>
|
|
|
+ <p
|
|
|
+ className={`${FONT_STYLES.caption}`}
|
|
|
+ style={{ color: COLORS.text.secondary }}
|
|
|
+ >
|
|
|
+ {item.company}
|
|
|
+ </p>
|
|
|
+ <div className="flex items-center mt-1 space-x-2">
|
|
|
+ <span
|
|
|
+ className={`${FONT_STYLES.small} px-2 py-1 rounded-full flex items-center`}
|
|
|
+ style={{
|
|
|
+ backgroundColor: COLORS.accent.blue,
|
|
|
+ color: 'white'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {item.salary}
|
|
|
</span>
|
|
|
- ))}
|
|
|
+ {item.tags.slice(0, 1).map((tag, i) => (
|
|
|
+ <span
|
|
|
+ key={i}
|
|
|
+ className={`${FONT_STYLES.small} px-2 py-1 rounded-full flex items-center`}
|
|
|
+ style={{
|
|
|
+ backgroundColor: COLORS.accent.green,
|
|
|
+ color: 'white'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <MapPinIcon className="w-3 h-3 mr-1" />
|
|
|
+ {tag}
|
|
|
+ </span>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- ))}
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ )}
|
|
|
|
|
|
{/* 热门知识 */}
|
|
|
- <div className="bg-white mt-2 p-4">
|
|
|
- <div className="flex justify-between items-center mb-4">
|
|
|
- <h2 className="text-lg font-bold text-gray-900">热门知识</h2>
|
|
|
- <button
|
|
|
- onClick={() => navigate('/silver-wisdom')}
|
|
|
- className="text-blue-600 text-sm"
|
|
|
- >
|
|
|
- 查看更多 →
|
|
|
- </button>
|
|
|
+ {hotKnowledge.length > 0 && (
|
|
|
+ <div className="mt-6 px-4">
|
|
|
+ <div className="flex justify-between items-center mb-4">
|
|
|
+ <h2
|
|
|
+ className={`${FONT_STYLES.sectionTitle}`}
|
|
|
+ style={{ color: COLORS.text.primary }}
|
|
|
+ >
|
|
|
+ 热门知识
|
|
|
+ </h2>
|
|
|
+ <button
|
|
|
+ onClick={() => navigate('/silver-wisdom')}
|
|
|
+ className={`${FONT_STYLES.caption} px-4 py-2 rounded-full border transition-all duration-300 hover:shadow-md flex items-center space-x-1`}
|
|
|
+ style={{
|
|
|
+ color: COLORS.text.primary,
|
|
|
+ borderColor: COLORS.ink.medium,
|
|
|
+ backgroundColor: 'transparent'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <span>显示更多</span>
|
|
|
+ <span>→</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="grid grid-cols-2 gap-3">
|
|
|
+ {hotKnowledge.map((item) => (
|
|
|
+ <div
|
|
|
+ key={item.id}
|
|
|
+ className="rounded-xl p-3 cursor-pointer transition-all duration-300 hover:shadow-lg backdrop-blur-sm"
|
|
|
+ style={{
|
|
|
+ backgroundColor: 'rgba(255,255,255,0.8)',
|
|
|
+ border: `1px solid ${COLORS.ink.medium}`
|
|
|
+ }}
|
|
|
+ onClick={() => navigate(`/silver-wisdom/${item.id}`)}
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ src={item.coverImage}
|
|
|
+ alt={item.title}
|
|
|
+ className="w-full h-24 object-cover rounded-lg mb-2"
|
|
|
+ />
|
|
|
+ <h4
|
|
|
+ className={`font-medium ${FONT_STYLES.caption} line-clamp-2`}
|
|
|
+ style={{ color: COLORS.text.primary }}
|
|
|
+ >
|
|
|
+ {item.title}
|
|
|
+ </h4>
|
|
|
+ <div className="flex items-center justify-between mt-1">
|
|
|
+ <span
|
|
|
+ className={`${FONT_STYLES.small}`}
|
|
|
+ style={{ color: COLORS.text.secondary }}
|
|
|
+ >
|
|
|
+ {item.category}
|
|
|
+ </span>
|
|
|
+ <div className="flex items-center">
|
|
|
+ <EyeIcon className="w-3 h-3 mr-1" style={{ color: COLORS.text.light }} />
|
|
|
+ <span
|
|
|
+ className={`${FONT_STYLES.small}`}
|
|
|
+ style={{ color: COLORS.text.light }}
|
|
|
+ >
|
|
|
+ {item.viewCount}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
-
|
|
|
- <div className="grid grid-cols-2 gap-4">
|
|
|
- {hotKnowledge.slice(0, 4).map((item) => (
|
|
|
- <div
|
|
|
- key={item.id}
|
|
|
- className="bg-gray-50 rounded-lg p-3 cursor-pointer hover:bg-gray-100 transition-colors"
|
|
|
- onClick={() => navigate(`/silver-wisdom/${item.id}`)}
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 银龄库 */}
|
|
|
+ {silverTalents.length > 0 && (
|
|
|
+ <div className="mt-6 px-4">
|
|
|
+ <div className="flex justify-between items-center mb-4">
|
|
|
+ <h2
|
|
|
+ className={`${FONT_STYLES.sectionTitle}`}
|
|
|
+ style={{ color: COLORS.text.primary }}
|
|
|
>
|
|
|
- <img
|
|
|
- src={item.coverImage}
|
|
|
- alt={item.title}
|
|
|
- className="w-full h-20 object-cover rounded mb-2"
|
|
|
- loading="lazy"
|
|
|
- />
|
|
|
- <h4 className="text-sm font-medium text-gray-900 line-clamp-2">{item.title}</h4>
|
|
|
- <div className="flex items-center justify-between mt-1">
|
|
|
- <span className="text-xs text-gray-500">{item.category}</span>
|
|
|
- <span className="text-xs text-gray-500">{item.viewCount}阅读</span>
|
|
|
+ 银龄人才
|
|
|
+ </h2>
|
|
|
+ <button
|
|
|
+ onClick={() => navigate('/silver-talents')}
|
|
|
+ className={`${FONT_STYLES.caption} px-4 py-2 rounded-full border transition-all duration-300 hover:shadow-md flex items-center space-x-1`}
|
|
|
+ style={{
|
|
|
+ color: COLORS.text.primary,
|
|
|
+ borderColor: COLORS.ink.medium,
|
|
|
+ backgroundColor: 'transparent'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <span>显示更多</span>
|
|
|
+ <span>→</span>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="space-y-3">
|
|
|
+ {silverTalents.map((talent) => (
|
|
|
+ <div
|
|
|
+ key={talent.id}
|
|
|
+ className="flex items-center p-4 rounded-xl shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer backdrop-blur-sm"
|
|
|
+ style={{
|
|
|
+ backgroundColor: 'rgba(255,255,255,0.8)',
|
|
|
+ border: `1px solid ${COLORS.ink.medium}`
|
|
|
+ }}
|
|
|
+ onClick={() => navigate(`/silver-talents/${talent.id}`)}
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ src={talent.avatar || '/images/avatar-placeholder.jpg'}
|
|
|
+ alt={talent.name}
|
|
|
+ className="w-16 h-16 rounded-full object-cover mr-4 border-2 shadow-sm"
|
|
|
+ style={{ borderColor: COLORS.ink.medium }}
|
|
|
+ />
|
|
|
+ <div className="flex-1">
|
|
|
+ <h4
|
|
|
+ className={`font-medium ${FONT_STYLES.body}`}
|
|
|
+ style={{ color: COLORS.text.primary }}
|
|
|
+ >
|
|
|
+ {talent.name}
|
|
|
+ </h4>
|
|
|
+ <p
|
|
|
+ className={`${FONT_STYLES.caption}`}
|
|
|
+ style={{ color: COLORS.text.secondary }}
|
|
|
+ >
|
|
|
+ {talent.specialty || '暂无特长信息'}
|
|
|
+ </p>
|
|
|
+ <div className="flex items-center mt-2 space-x-2">
|
|
|
+ <span
|
|
|
+ className={`${FONT_STYLES.small} px-3 py-1 rounded-full flex items-center`}
|
|
|
+ style={{
|
|
|
+ backgroundColor: COLORS.accent.blue,
|
|
|
+ color: 'white'
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <MapPinIcon className="w-3 h-3 mr-1" />
|
|
|
+ {talent.city || '未知地区'}
|
|
|
+ </span>
|
|
|
+ <span
|
|
|
+ className={`${FONT_STYLES.small} flex items-center`}
|
|
|
+ style={{ color: COLORS.text.light }}
|
|
|
+ >
|
|
|
+ <CalendarDaysIcon className="w-3 h-3 mr-1" />
|
|
|
+ {new Date(talent.createdAt).toLocaleDateString()}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- ))}
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ )}
|
|
|
|
|
|
{/* 时间银行活动 */}
|
|
|
{timeBankActivities.length > 0 && (
|
|
|
- <div className="bg-white mt-2 p-4">
|
|
|
+ <div className="mt-6 px-4">
|
|
|
<div className="flex justify-between items-center mb-4">
|
|
|
- <h2 className="text-lg font-bold text-gray-900">时间银行活动</h2>
|
|
|
+ <h2
|
|
|
+ className={`${FONT_STYLES.sectionTitle}`}
|
|
|
+ style={{ color: COLORS.text.primary }}
|
|
|
+ >
|
|
|
+ 时间银行活动
|
|
|
+ </h2>
|
|
|
<button
|
|
|
onClick={() => navigate('/time-bank')}
|
|
|
- className="text-blue-600 text-sm"
|
|
|
+ className={`${FONT_STYLES.caption} px-4 py-2 rounded-full border transition-all duration-300 hover:shadow-md flex items-center space-x-1`}
|
|
|
+ style={{
|
|
|
+ color: COLORS.text.primary,
|
|
|
+ borderColor: COLORS.ink.medium,
|
|
|
+ backgroundColor: 'transparent'
|
|
|
+ }}
|
|
|
>
|
|
|
- 查看更多 →
|
|
|
+ <span>查看更多</span>
|
|
|
+ <span>→</span>
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
@@ -307,26 +624,81 @@ const NewHomePage: React.FC = () => {
|
|
|
{timeBankActivities.slice(0, 2).map((activity) => (
|
|
|
<div
|
|
|
key={activity.id}
|
|
|
- className="flex items-center justify-between p-3 bg-blue-50 rounded-lg"
|
|
|
+ className="flex items-center justify-between p-4 rounded-xl shadow-sm backdrop-blur-sm"
|
|
|
+ style={{
|
|
|
+ backgroundColor: 'rgba(255,255,255,0.8)',
|
|
|
+ border: `1px solid ${COLORS.ink.medium}`
|
|
|
+ }}
|
|
|
>
|
|
|
<div>
|
|
|
- <h4 className="font-medium text-gray-900">{activity.organization}</h4>
|
|
|
- <p className="text-sm text-gray-600">
|
|
|
- {activity.workType === 1 ? '志愿服务' : '技能培训'} · {activity.workHours}小时
|
|
|
+ <h4
|
|
|
+ className={`font-medium ${FONT_STYLES.body}`}
|
|
|
+ style={{ color: COLORS.text.primary }}
|
|
|
+ >
|
|
|
+ {activity.organization}
|
|
|
+ </h4>
|
|
|
+ <p
|
|
|
+ className={`${FONT_STYLES.caption}`}
|
|
|
+ style={{ color: COLORS.text.secondary }}
|
|
|
+ >
|
|
|
+ {activity.workType} · {activity.workHours}小时
|
|
|
</p>
|
|
|
</div>
|
|
|
<div className="text-right">
|
|
|
- <div className="text-lg font-bold text-blue-600">{activity.earnedPoints}</div>
|
|
|
- <div className="text-xs text-gray-500">积分奖励</div>
|
|
|
+ <div
|
|
|
+ className={`text-lg font-bold ${FONT_STYLES.body}`}
|
|
|
+ style={{ color: COLORS.accent.blue }}
|
|
|
+ >
|
|
|
+ {activity.earnedPoints}
|
|
|
+ </div>
|
|
|
+ <div className="flex items-center">
|
|
|
+ <TrophyIcon className="w-3 h-3 mr-1" style={{ color: COLORS.text.light }} />
|
|
|
+ <div
|
|
|
+ className={`${FONT_STYLES.small}`}
|
|
|
+ style={{ color: COLORS.text.light }}
|
|
|
+ >
|
|
|
+ 积分
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
))}
|
|
|
</div>
|
|
|
</div>
|
|
|
)}
|
|
|
+
|
|
|
+ {/* 底部空间 */}
|
|
|
+ <div className="h-8"></div>
|
|
|
</div>
|
|
|
+
|
|
|
+ {/* 刷新按钮 */}
|
|
|
+ <button
|
|
|
+ onClick={handlePullToRefresh}
|
|
|
+ disabled={isPulling}
|
|
|
+ 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>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
-export default NewHomePage;
|
|
|
+// 水墨风格骨架屏
|
|
|
+const HeaderSkeleton: React.FC = () => (
|
|
|
+ <div className="px-4 py-4" style={{ backgroundColor: COLORS.ink.light }}>
|
|
|
+ <div className="flex items-center justify-between mb-3">
|
|
|
+ <div className="h-8 rounded" style={{ backgroundColor: COLORS.ink.medium, width: '8rem' }}></div>
|
|
|
+ <div className="h-8 rounded-full" style={{ backgroundColor: COLORS.ink.medium, width: '4rem' }}></div>
|
|
|
+ </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>
|
|
|
+);
|
|
|
+
|
|
|
+export default NewHomePage;
|