Răsfoiți Sursa

增加了轮播

yourname 7 luni în urmă
părinte
comite
5676743618

+ 107 - 0
docs/ad-banner-carousel-implementation.md

@@ -0,0 +1,107 @@
+# 广告位轮播实现方案
+
+## 项目概述
+在银龄智慧首页的银龄图标上方添加一个广告位轮播图,保持与现有水墨风格界面设计一致。
+
+## 技术实现方案
+
+### 1. 组件设计
+- **组件名称**: AdBannerCarousel
+- **位置**: src/client/mobile/components/AdBannerCarousel.tsx
+- **样式风格**: 延续中国水墨风格,使用现有色彩方案
+
+### 2. 数据结构
+```typescript
+interface AdBannerItem {
+  id: number;
+  title: string;
+  description: string;
+  image: string;
+  link: string;
+  sortOrder: number;
+}
+```
+
+### 3. 样式规范
+- **背景色**: 使用宣纸背景色 `#f5f3f0`
+- **边框**: 淡墨色 `#d4c4a8` 
+- **文字**: 墨色文字 `#2f1f0f`
+- **圆角**: 匹配现有设计,使用 `border-radius: 6px`
+- **阴影**: `shadow-sm transition-all duration-300 hover:shadow-md`
+
+### 4. 组件特性
+- 自动轮播(每4秒切换)
+- 手动控制(左右箭头)
+- 指示器(底部小圆点)
+- 响应式设计
+- 图片懒加载
+- 错误图片回退机制
+
+### 5. 位置布局
+- **垂直位置**: 服务分类区域上方(银龄图标网格上方)
+- **水平位置**: 全宽展示,左右边距16px
+- **高度**: 120px(适合移动设备展示)
+
+### 6. 图片资源
+- 使用现有banner图片:`/images/banner1.jpg`, `/images/banner2.jpg`
+- 添加示例广告图片到 `/public/images/ad-banner-*.jpg`
+- 备用图片:`/images/placeholder-banner.jpg`
+
+### 7. 集成步骤
+1. 创建AdBannerCarousel组件
+2. 修改NewHomePage.tsx,在服务分类区域上方插入广告轮播
+3. 添加示例广告数据
+4. 测试轮播功能
+5. 优化移动端体验
+
+### 8. 响应式处理
+- **移动端**: 全宽展示,高度120px
+- **平板**: 保持相同设计,适配更大屏幕
+- **桌面**: 在移动视图内保持一致体验
+
+### 9. 性能优化
+- 图片懒加载
+- 自动轮播暂停(用户悬停时)
+- 内存管理(组件卸载时清理定时器)
+
+### 10. 错误处理
+- 图片加载失败时显示占位图
+- 无广告数据时隐藏组件
+- 网络异常时的优雅降级
+
+## 实现代码结构
+
+### AdBannerCarousel组件
+```typescript
+// 基础的自动轮播组件
+const AdBannerCarousel: React.FC<{
+  ads: AdBannerItem[];
+  autoPlayInterval?: number;
+}> = ({ ads, autoPlayInterval = 4000 }) => {
+  // 轮播逻辑
+}
+```
+
+### 样式配置
+```css
+.ad-banner-container {
+  background-color: rgba(255,255,255,0.7);
+  border: 1px solid #d4c4a8;
+  border-radius: 12px;
+  margin: 16px;
+  overflow: hidden;
+}
+```
+
+## 测试方案
+1. 手动切换测试
+2. 自动轮播测试
+3. 图片加载测试
+4. 响应式适配测试
+5. 性能测试
+
+## 部署计划
+1. 开发环境测试
+2. 生产环境部署
+3. 监控用户反馈
+4. 性能监控

+ 206 - 0
src/client/mobile/components/AdBannerCarousel.tsx

@@ -0,0 +1,206 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { useNavigate } from 'react-router-dom';
+
+// 水墨风格色彩配置
+const COLORS = {
+  ink: {
+    light: '#f5f3f0',
+    medium: '#d4c4a8',
+    dark: '#8b7355',
+    deep: '#3a2f26',
+  },
+  text: {
+    primary: '#2f1f0f',
+    secondary: '#5d4e3b',
+  }
+};
+
+interface AdBannerItem {
+  id: number;
+  title: string;
+  description: string;
+  image: string;
+  link: string;
+}
+
+interface AdBannerCarouselProps {
+  ads?: AdBannerItem[];
+  autoPlayInterval?: number;
+}
+
+export const AdBannerCarousel: React.FC<AdBannerCarouselProps> = ({
+  ads = [],
+  autoPlayInterval = 4000
+}) => {
+  const [currentSlide, setCurrentSlide] = useState(0);
+  const [imageErrors, setImageErrors] = useState<Set<number>>(new Set());
+  const navigate = useNavigate();
+  const autoplayRef = useRef<NodeJS.Timeout | null>(null);
+
+  // 默认广告数据
+  const defaultAds: AdBannerItem[] = [
+    {
+      id: 1,
+      title: "银龄智慧平台上线",
+      description: "专为银龄人群打造的智慧生活服务平台",
+      image: "/images/banner1.jpg",
+      link: "/about"
+    },
+    {
+      id: 2,
+      title: "免费技能培训",
+      description: "多门实用技能课程,助力银龄再就业",
+      image: "/images/banner2.jpg",
+      link: "/elderly-university"
+    },
+    {
+      id: 3,
+      title: "时间银行招募",
+      description: "存储时间,收获温暖,共建互助社区",
+      image: "/images/placeholder-banner.jpg",
+      link: "/time-bank"
+    }
+  ];
+
+  const displayAds = ads.length > 0 ? ads : defaultAds;
+
+  // 自动播放控制
+  const startAutoplay = () => {
+    if (autoplayRef.current) {
+      clearInterval(autoplayRef.current);
+    }
+    
+    if (displayAds.length > 1) {
+      autoplayRef.current = setInterval(() => {
+        setCurrentSlide((prev) => (prev + 1) % displayAds.length);
+      }, autoPlayInterval);
+    }
+  };
+
+  const stopAutoplay = () => {
+    if (autoplayRef.current) {
+      clearInterval(autoplayRef.current);
+    }
+  };
+
+  // 启动/停止自动播放
+  useEffect(() => {
+    startAutoplay();
+    
+    return () => stopAutoplay();
+  }, [displayAds.length, autoPlayInterval]);
+
+  const handleImageError = (index: number) => {
+    setImageErrors(prev => new Set(prev).add(index));
+  };
+
+  const handleSlideClick = (link: string) => {
+    navigate(link);
+  };
+
+  const goToSlide = (index: number) => {
+    setCurrentSlide(index);
+  };
+
+  const handlePrev = () => {
+    setCurrentSlide((prev) => (prev - 1 + displayAds.length) % displayAds.length);
+  };
+
+  const handleNext = () => {
+    setCurrentSlide((prev) => (prev + 1) % displayAds.length);
+  };
+
+  if (!displayAds || displayAds.length === 0) {
+    return null;
+  }
+
+  return (
+    <div 
+      className="relative mx-4 my-3 overflow-hidden rounded-xl shadow-sm"
+      style={{
+        backgroundColor: 'rgba(255,255,255,0.7)',
+        border: `1px solid ${COLORS.ink.medium}`,
+      }}
+      onTouchStart={stopAutoplay}
+      onTouchEnd={startAutoplay}
+    >
+      <div className="relative h-32">
+        <div
+          className="flex transition-transform duration-500 ease-in-out"
+          style={{ transform: `translateX(-${currentSlide * 100}%)` }}
+        >
+          {displayAds.map((item, index) => (
+            <div
+              key={item.id}
+              className="w-full flex-shrink-0 relative cursor-pointer"
+              onClick={() => handleSlideClick(item.link)}
+            >
+              <img
+                src={imageErrors.has(index) ? '/images/placeholder-banner.jpg' : item.image}
+                alt={item.title}
+                className="w-full h-32 object-cover"
+                onError={() => handleImageError(index)}
+                loading="lazy"
+              />
+              <div className="absolute inset-0 bg-gradient-to-t from-black/40 via-black/10 to-transparent">
+                <div className="absolute bottom-3 left-3 right-3 text-white">
+                  <h3 className="text-sm font-bold mb-0.5 line-clamp-1">{item.title}</h3>
+                  <p className="text-xs opacity-90 line-clamp-2">{item.description}</p>
+                </div>
+              </div>
+            </div>
+          ))}
+        </div>
+
+        {/* 轮播指示器 */}
+        {displayAds.length > 1 && (
+          <div className="absolute bottom-2 left-1/2 transform -translate-x-1/2 flex space-x-1.5">
+            {displayAds.map((_, index) => (
+              <button
+                key={index}
+                onClick={() => goToSlide(index)}
+                className={`w-1.5 h-1.5 rounded-full transition-all duration-200 ${
+                  index === currentSlide 
+                    ? 'bg-white w-4' 
+                    : 'bg-white/60 hover:bg-white/80'
+                }`}
+                aria-label={`跳转到第${index + 1}张`}
+              />
+            ))}
+          </div>
+        )}
+
+        {/* 左右箭头 - 移动端触摸时显示 */}
+        {displayAds.length > 1 && (
+          <>
+            <button
+              onClick={handlePrev}
+              className="absolute left-1 top-1/2 -translate-y-1/2 bg-black/20 hover:bg-black/40 text-white p-1.5 rounded-full transition-all duration-200 opacity-80 active:opacity-100"
+              aria-label="上一张"
+            >
+              <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
+              </svg>
+            </button>
+            <button
+              onClick={handleNext}
+              className="absolute right-1 top-1/2 -translate-y-1/2 bg-black/20 hover:bg-black/40 text-white p-1.5 rounded-full transition-all duration-200 opacity-80 active:opacity-100"
+              aria-label="下一张"
+            >
+              <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
+              </svg>
+            </button>
+          </>
+        )}
+      </div>
+
+      {/* 进度指示器 */}
+      {displayAds.length > 1 && (
+        <div className="absolute bottom-2 right-2 bg-black/30 text-white text-xs px-1.5 py-0.5 rounded">
+          {currentSlide + 1} / {displayAds.length}
+        </div>
+      )}
+    </div>
+  );
+};

+ 1 - 389
src/client/mobile/pages/HomePage.tsx

@@ -1,9 +1,4 @@
-import React, { useState, useEffect, useRef } from 'react';
-import { useNavigate } from 'react-router-dom';
-import { useHomeData } from '../hooks/useHomeData';
-import { useAuth } from '../hooks/AuthProvider';
-import { EnhancedCarousel } from '../components/EnhancedCarousel';
-import { SkeletonLoader, BannerSkeleton, ListItemSkeleton, UserStatsSkeleton } from '../components/SkeletonLoader';
+import React from 'react';
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
 import NewHomePage from './NewHomePage';
 
@@ -18,389 +13,6 @@ const queryClient = new QueryClient({
   },
 });
 
-// 数据转换工具
-const transformPolicyNews = (news: any[]) => 
-  news.map(item => ({
-    id: item.id,
-    title: item.newsTitle,
-    description: item.summary || item.newsContent.substring(0, 100) + '...',
-    image: item.images?.split(',')[0] || '/images/banner1.jpg',
-    fallbackImage: '/images/placeholder-banner.jpg',
-    link: `/policy-news/${item.id}`
-  }));
-
-const transformJobs = (jobs: any[]) =>
-  jobs.map(job => ({
-    id: job.id,
-    title: job.title,
-    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] : '全国', '热门']
-  }));
-
-const transformKnowledge = (knowledge: any[]) =>
-  knowledge.map(item => ({
-    id: item.id,
-    title: item.title,
-    category: item.category?.name || '其他',
-    coverImage: item.coverImage || `https://picsum.photos/seed/${item.id}/200/200`,
-    viewCount: item.viewCount || 0
-  }));
-
-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()
-  }));
-
-// 主HomePage组件
-const HomeContent: 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,
-    isError,
-    error,
-    refetch
-  } = useHomeData();
-
-  // 下拉刷新处理
-  const handlePullToRefresh = async () => {
-    setIsPulling(true);
-    try {
-      await refetch();
-    } finally {
-      setIsPulling(false);
-    }
-  };
-
-  // 搜索处理
-  const handleSearch = () => {
-    if (searchQuery.trim()) {
-      navigate(`/search?q=${encodeURIComponent(searchQuery)}`);
-    }
-  };
-
-  const handleKeyPress = (e: React.KeyboardEvent) => {
-    if (e.key === 'Enter') {
-      handleSearch();
-    }
-  };
-
-  // 错误状态
-  if (isError) {
-    return (
-      <div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
-        <div className="text-center bg-white p-8 rounded-lg shadow-sm">
-          <div className="text-red-500 text-6xl mb-4">⚠️</div>
-          <h3 className="text-lg font-bold text-gray-900 mb-2">加载失败</h3>
-          <p className="text-gray-600 mb-4">{error?.message || '获取首页数据失败'}</p>
-          <button
-            onClick={() => refetch()}
-            className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors"
-          >
-            重新加载
-          </button>
-        </div>
-      </div>
-    );
-  }
-
-  // 加载状态
-  if (isLoading || !homeData) {
-    return (
-      <div className="min-h-screen bg-gray-50">
-        <HeaderSkeleton />
-        <div className="p-4 space-y-4">
-          <BannerSkeleton />
-          <CategorySkeleton />
-          <ListItemSkeleton count={3} />
-          <ListItemSkeleton count={4} />
-        </div>
-      </div>
-    );
-  }
-
-  // 数据转换
-  const banners = transformPolicyNews(homeData.banners || []);
-  const recommendedJobs = transformJobs(homeData.recommendedJobs || []);
-  const hotKnowledge = transformKnowledge(homeData.hotKnowledge || []);
-  const timeBankActivities = transformTimeBank(homeData.timeBankActivities || []);
-
-  return (
-    <div className="min-h-screen bg-gray-50">
-      {/* 顶部导航栏 */}
-      <header className="bg-white shadow-sm sticky top-0 z-10">
-        <div className="px-4 py-3">
-          <div className="flex items-center justify-between mb-3">
-            <h1 className="text-xl font-bold text-blue-600">银龄智慧平台</h1>
-            {user ? (
-              <div className="flex items-center space-x-2">
-                <span className="text-sm text-gray-600 hidden sm:block">欢迎,{user.username}</span>
-                <img 
-                  src={user.avatar || '/images/avatar-placeholder.jpg'} 
-                  alt="用户头像"
-                  className="w-8 h-8 rounded-full object-cover"
-                />
-              </div>
-            ) : (
-              <button 
-                onClick={() => navigate('/login')}
-                className="text-blue-600 text-sm px-3 py-1 rounded-full border border-blue-600 hover:bg-blue-50 transition-colors"
-              >
-                登录
-              </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>
-            <input
-              ref={searchRef}
-              type="text"
-              placeholder="搜索岗位、知识、企业..."
-              value={searchQuery}
-              onChange={(e) => setSearchQuery(e.target.value)}
-              onKeyPress={handleKeyPress}
-              className="flex-1 bg-transparent outline-none text-sm"
-            />
-            <button
-              onClick={handleSearch}
-              className="text-blue-600 text-xs px-2 hover:text-blue-800 transition-colors"
-            >
-              搜索
-            </button>
-          </div>
-        </div>
-      </header>
-
-      {/* 下拉刷新指示器 */}
-      {isPulling && (
-        <div className="bg-blue-50 text-blue-600 text-center py-2">
-          <div className="flex items-center justify-center">
-            <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2"></div>
-            正在刷新...
-          </div>
-        </div>
-      )}
-
-      {/* 主要内容 */}
-      <div className="pb-16">
-        {/* 轮播图 */}
-        {banners.length > 0 && (
-          <div className="bg-white">
-            <EnhancedCarousel 
-              items={banners} 
-              autoPlayInterval={5000}
-              className="rounded-b-lg"
-            />
-          </div>
-        )}
-
-        {/* 用户统计 - 已移动到个人中心 */}
-        
-
-        {/* 服务分类 */}
-        <div className="bg-white mt-2 p-4">
-          <div className="grid grid-cols-3 gap-3">
-            {[
-              { 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) => (
-              <button
-                key={index}
-                onClick={() => navigate(category.path)}
-                className="flex flex-col items-center p-2 rounded-lg hover:bg-gray-50 transition-colors"
-              >
-                <div className={`${category.color} text-white w-12 h-12 rounded-full flex items-center justify-center text-xl mb-1`}>
-                  {category.icon}
-                </div>
-                <span className="text-xs text-gray-700">{category.name}</span>
-              </button>
-            ))}
-          </div>
-        </div>
-
-        {/* 推荐岗位 */}
-        {recommendedJobs.length > 0 && (
-          <div className="bg-white mt-2 p-4">
-            <div className="flex justify-between items-center mb-3">
-              <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}`)}
-                >
-                  <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 text-sm line-clamp-1">{item.title}</h4>
-                    <p className="text-xs 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}
-                        </span>
-                      ))}
-                    </div>
-                  </div>
-                </div>
-              ))}
-            </div>
-          </div>
-        )}
-
-        {/* 热门知识 */}
-        {hotKnowledge.length > 0 && (
-          <div className="bg-white mt-2 p-4">
-            <div className="flex justify-between items-center mb-3">
-              <h2 className="text-lg font-bold text-gray-900">热门知识</h2>
-              <button 
-                onClick={() => navigate('/silver-wisdom')}
-                className="text-blue-600 text-sm"
-              >
-                查看更多 →
-              </button>
-            </div>
-            
-            <div className="grid grid-cols-2 gap-3">
-              {hotKnowledge.slice(0, 4).map((item) => (
-                <div
-                  key={item.id}
-                  className="bg-gray-50 rounded-lg p-2 cursor-pointer hover:bg-gray-100 transition-colors"
-                  onClick={() => navigate(`/silver-wisdom/${item.id}`)}
-                >
-                  <img
-                    src={item.coverImage}
-                    alt={item.title}
-                    className="w-full h-20 object-cover rounded mb-1"
-                    loading="lazy"
-                  />
-                  <h4 className="text-xs 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>
-                  </div>
-                </div>
-              ))}
-            </div>
-          </div>
-        )}
-
-        {/* 时间银行活动 */}
-        {timeBankActivities.length > 0 && (
-          <div className="bg-white mt-2 p-4">
-            <div className="flex justify-between items-center mb-3">
-              <h2 className="text-lg font-bold text-gray-900">时间银行活动</h2>
-              <button 
-                onClick={() => navigate('/time-bank')}
-                className="text-blue-600 text-sm"
-              >
-                查看更多 →
-              </button>
-            </div>
-            
-            <div className="space-y-3">
-              {timeBankActivities.slice(0, 2).map((activity) => (
-                <div
-                  key={activity.id}
-                  className="flex items-center justify-between p-3 bg-blue-50 rounded-lg"
-                >
-                  <div>
-                    <h4 className="font-medium text-gray-900 text-sm">{activity.organization}</h4>
-                    <p className="text-xs text-gray-600">
-                      {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>
-                </div>
-              ))}
-            </div>
-          </div>
-        )}
-
-        {/* 底部空间 */}
-        <div className="h-8"></div>
-      </div>
-
-      {/* 刷新按钮 */}
-      <button
-        onClick={handlePullToRefresh}
-        className="fixed bottom-20 right-4 bg-blue-600 text-white p-3 rounded-full shadow-lg"
-        disabled={isPulling}
-      >
-        <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
-        </svg>
-      </button>
-    </div>
-  );
-};
-
-// Header骨架屏
-const HeaderSkeleton: React.FC = () => (
-  <div className="bg-white shadow-sm 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>
-);
-
-// 分类骨架屏
-const CategorySkeleton: React.FC = () => (
-  <div className="grid grid-cols-3 gap-3 p-4">
-    {Array.from({ length: 6 }).map((_, i) => (
-      <div key={i} className="flex flex-col items-center">
-        <div className="w-12 h-12 bg-gray-200 rounded-full mb-1 animate-pulse"></div>
-        <div className="h-3 bg-gray-200 rounded w-10 animate-pulse"></div>
-      </div>
-    ))}
-  </div>
-);
-
 // 主组件包装
 const HomePage: React.FC = () => {
   return (

+ 543 - 171
src/client/mobile/pages/NewHomePage.tsx

@@ -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;

+ 5 - 1
src/server/modules/silver-users/silver-user-profile.dto.ts

@@ -150,4 +150,8 @@ export const SilverUserProfileSchema = z.object({
     description: '认证信息',
     example: '教师资格证,心理咨询师证'
   }),
-  jobSeekingStatus: z.number().int
+  jobSeekingStatus: z.number().int().min(0).max(2).openapi({
+    description: '求职状态 0:不求职 1:积极求职 2:观望中',
+    example: 1
+  })
+});

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
tsconfig.tsbuildinfo


Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff