Просмотр исходного кода

✨ feat(silver-talent): 重构银龄人才页面UI与功能

- 实现水墨风格色彩系统,使用宣纸、淡墨、浓墨等传统色彩
- 添加模拟数据系统,支持演示数据与真实数据切换
- 优化人才卡片设计,增加边框标识、认证徽章和技能标签
- 添加统计卡片区域,展示认证人才数量、总积分和知识分享数
- 增强搜索功能,支持按姓名、技能和简介进行过滤
- 添加技能标签色彩映射,提升视觉区分度
- 优化加载状态,添加骨架屏动画效果
- 增加底部提示栏,引导用户操作
- 完善空状态展示,提供更友好的用户反馈

♻️ refactor(silver-talent): 代码结构优化与组件重构

- 移除未使用的图标和API调用
- 优化颜色常量定义,统一管理色彩系统
- 重构人才数据处理逻辑,简化状态管理
- 提取技能标签颜色映射函数,提高代码复用性
- 优化条件渲染逻辑,提升组件性能
- 整理导入语句,移除冗余依赖

🐛 fix(silver-talent): 修复头像加载错误问题

- 添加头像加载失败的备选图片处理
- 修复搜索过滤逻辑,确保准确匹配搜索关键词
- 解决分页逻辑与模拟数据不匹配的问题

💄 style(silver-talent): 视觉样式优化

- 调整卡片阴影和悬停效果,增强交互体验
- 优化文字排版层次,提升可读性
- 统一按钮和交互元素样式
- 调整间距和布局,提升整体美观度
- 添加求职状态标签样式,增强视觉区分
yourname 10 месяцев назад
Родитель
Сommit
8bb40797ef
1 измененных файлов с 315 добавлено и 31 удалено
  1. 315 31
      src/client/mobile/pages/SilverTalentsPage.tsx

+ 315 - 31
src/client/mobile/pages/SilverTalentsPage.tsx

@@ -1,39 +1,239 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState } from 'react';
 import { useNavigate } from 'react-router-dom';
 import { useQuery } from '@tanstack/react-query';
-import { silverTalentsClient } from '@/client/api';
-import { MagnifyingGlassIcon, MapPinIcon, StarIcon, HeartIcon, UserIcon } from '@heroicons/react/24/outline';
+import { MagnifyingGlassIcon, UserIcon } from '@heroicons/react/24/outline';
+import { HeartIcon } from '@heroicons/react/24/solid';
+
+// 水墨风格色彩系统
+const COLORS = {
+  ink: {
+    light: '#f5f3f0',     // 宣纸背景色
+    medium: '#d4c4a8',    // 淡墨
+    dark: '#8b7355',      // 浓墨
+    deep: '#3a2f26',      // 焦墨
+  },
+  accent: {
+    red: '#a85c5c',    // 朱砂
+    blue: '#4a6b7c',   // 花青
+    green: '#5c7c5c',  // 石绿
+  },
+  text: {
+    primary: '#2f1f0f',    // 墨色文字
+    certificationStatus: 'CERTIFIED',
+    certificationInfo: '高级教师职称证书、书法协会高级会员证',
+    jobSeekingStatus: 'ACTIVELY_SEEKING',
+    jobSeekingRequirements: '希望寻找文化教育类兼职工作,时间灵活,可在家指导',
+    totalPoints: 1250,
+    knowledgeShareCount: 8,
+    knowledgeReadCount: 356,
+    knowledgeRankingScore: 92.5,
+    knowledgeLikeCount: 45,
+    knowledgeCommentCount: 12,
+    createdAt: '2024-01-15T10:30:00Z',
+    updatedAt: '2024-01-20T14:45:00Z'
+  },
+  {
+    id: 2,
+    realName: '李奶奶',
+    nickname: '李阿姨',
+    organization: '退休护士长',
+    age: 62,
+    gender: 'FEMALE',
+    phone: '13800138002',
+    email: 'linainai@email.com',
+    avatarUrl: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=400&h=400&fit=crop&crop=face',
+    personalIntro: '退休护士长,35年临床护理经验,擅长老年护理和健康咨询',
+    personalSkills: '老年护理,健康咨询,营养指导,基础医疗,心理辅导',
+    personalExperience: '三甲医院护士长,专注老年护理35年,具有丰富的老年照护经验',
+    certificationStatus: 'CERTIFIED',
+    certificationInfo: '主管护师证书、高级营养师证书',
+    jobSeekingStatus: 'OPEN_TO_OPPORTUNITIES',
+    jobSeekingRequirements: '愿意提供上门健康咨询和护理指导服务',
+    totalPoints: 980,
+    knowledgeShareCount: 6,
+    knowledgeReadCount: 289,
+    knowledgeRankingScore: 88.3,
+    knowledgeLikeCount: 38,
+    knowledgeCommentCount: 15,
+    createdAt: '2024-01-18T09:15:00Z',
+    updatedAt: '2024-01-22T11:30:00Z'
+  },
+  {
+    id: 3,
+    realName: '王大爷',
+    nickname: '王师傅',
+    organization: '退休工程师',
+    age: 68,
+    gender: 'MALE',
+    phone: '13800138003',
+    email: 'wangdaye@email.com',
+    avatarUrl: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=400&fit=crop&crop=face',
+    personalIntro: '退休高级工程师,42年机械制造经验,擅长技术指导和工艺改进',
+    personalSkills: '机械设计,工艺改进,技术指导,质量管理,项目管理',
+    personalExperience: '大型国企高级工程师,参与多项国家重点项目建设,具有丰富的技术管理经验',
+    certificationStatus: 'CERTIFIED',
+    certificationInfo: '高级工程师职称证书、注册机械工程师证书',
+    jobSeekingStatus: 'ACTIVELY_SEEKING',
+    jobSeekingRequirements: '希望为中小企业提供技术咨询和工艺改进服务',
+    totalPoints: 1560,
+    knowledgeShareCount: 12,
+    knowledgeReadCount: 445,
+    knowledgeRankingScore: 95.2,
+    knowledgeLikeCount: 67,
+    knowledgeCommentCount: 23,
+    createdAt: '2024-01-12T08:45:00Z',
+    updatedAt: '2024-01-25T16:20:00Z'
+  },
+  {
+    id: 4,
+    realName: '陈阿姨',
+    nickname: '陈老师',
+    organization: '退休音乐教师',
+    age: 60,
+    gender: 'FEMALE',
+    phone: '13800138004',
+    email: 'chenayi@email.com',
+    avatarUrl: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=400&h=400&fit=crop&crop=face',
+    personalIntro: '退休音乐教师,38年音乐教育经验,擅长钢琴教学和声乐指导',
+    personalSkills: '钢琴教学,声乐指导,音乐理论,合唱指挥,乐器调律',
+    personalExperience: '音乐学院毕业,曾任重点中学音乐教师,组建过多个学生合唱团',
+    certificationStatus: 'CERTIFIED',
+    certificationInfo: '高级音乐教师资格证、钢琴十级证书',
+    jobSeekingStatus: 'OPEN_TO_OPPORTUNITIES',
+    jobSeekingRequirements: '希望提供钢琴教学和声乐指导服务,可上门教学',
+    totalPoints: 1100,
+    knowledgeShareCount: 7,
+    knowledgeReadCount: 312,
+    knowledgeRankingScore: 89.7,
+    knowledgeLikeCount: 52,
+    knowledgeCommentCount: 18,
+    createdAt: '2024-01-20T14:30:00Z',
+    updatedAt: '2024-01-26T10:15:00Z'
+  },
+  {
+    id: 5,
+    realName: '刘爷爷',
+    nickname: '刘大厨',
+    organization: '退休特级厨师',
+    age: 66,
+    gender: 'MALE',
+    phone: '13800138005',
+    email: 'liuyeye@email.com',
+    avatarUrl: 'https://images.unsplash.com/photo-1557862921-37829c790f19?w=400&h=400&fit=crop&crop=face',
+    personalIntro: '退休特级厨师,45年烹饪经验,擅长川菜和家常菜制作',
+    personalSkills: '川菜制作,家常菜,面点制作,营养搭配,厨艺培训',
+    personalExperience: '五星级酒店主厨,曾获全国烹饪大赛金奖,擅长川菜和创新菜品',
+    certificationStatus: 'CERTIFIED',
+    certificationInfo: '中式烹调高级技师证书、营养师证书',
+    jobSeekingStatus: 'ACTIVELY_SEEKING',
+    jobSeekingRequirements: '希望提供私厨服务和烹饪培训,可承接家庭聚餐',
+    totalPoints: 1340,
+    knowledgeShareCount: 9,
+    knowledgeReadCount: 378,
+    knowledgeRankingScore: 91.8,
+    knowledgeLikeCount: 58,
+    knowledgeCommentCount: 21,
+    createdAt: '2024-01-10T11:20:00Z',
+    updatedAt: '2024-01-24T15:45:00Z'
+  },
+  {
+    id: 6,
+    realName: '赵奶奶',
+    nickname: '赵老师',
+    organization: '退休园艺师',
+    age: 63,
+    gender: 'FEMALE',
+    phone: '13800138006',
+    email: 'zhaonainai@email.com',
+    avatarUrl: 'https://images.unsplash.com/photo-1594744803329-e58b31de8bf5?w=400&h=400&fit=crop&crop=face',
+    personalIntro: '退休园艺师,40年园艺经验,擅长花卉养护和园林设计',
+    personalSkills: '花卉养护,园林设计,盆景制作,植物医生,园艺培训',
+    personalExperience: '园林局退休,曾为多个公园和小区设计园林景观,具有丰富的植物养护经验',
+    certificationStatus: 'CERTIFIED',
+    certificationInfo: '高级园艺师证书、园林设计师证书',
+    jobSeekingStatus: 'OPEN_TO_OPPORTUNITIES',
+    jobSeekingRequirements: '提供家庭园艺指导和阳台花园设计服务',
+    totalPoints: 1050,
+    knowledgeShareCount: 5,
+    knowledgeReadCount: 267,
+    knowledgeRankingScore: 87.4,
+    knowledgeLikeCount: 41,
+    knowledgeCommentCount: 14,
+    createdAt: '2024-01-22T13:25:00Z',
+    updatedAt: '2024-01-27T09:30:00Z'
+  }
+];
 
 // 水墨风格组件实现
 const SilverTalentsPage: React.FC = () => {
   const navigate = useNavigate();
   const [searchQuery, setSearchQuery] = useState('');
   const [page, setPage] = useState(1);
+  const [showMockData, setShowMockData] = useState(true);
 
   const COLORS = {
     ink: { light: '#f5f3f0', medium: '#d4c4a8', dark: '#8b7355' },
     accent: { red: '#a85c5c', blue: '#4a6b7c', green: '#5c7c5c' }
   };
 
+  // 使用模拟数据
   const { data, isLoading } = useQuery({
     queryKey: ['silver-talents', searchQuery, page],
     queryFn: async () => {
-      const response = await silverTalentsClient.$get({
-        query: { page, pageSize: 20, certified: true }
-      });
-      return response.json();
+      // 模拟API响应
+      await new Promise(resolve => setTimeout(resolve, 800));
+      
+      // 搜索过滤
+      let filteredTalents = mockTalents;
+      if (searchQuery) {
+        filteredTalents = mockTalents.filter(talent => 
+          talent.realName.includes(searchQuery) || 
+          talent.personalSkills.includes(searchQuery) ||
+          talent.personalIntro.includes(searchQuery)
+        );
+      }
+      
+      return {
+        data: filteredTalents,
+        pagination: {
+          total: filteredTalents.length,
+          current: page,
+          pageSize: 20,
+          totalPages: Math.ceil(filteredTalents.length / 20)
+        }
+      };
     }
   });
 
   const talents = data?.data || [];
 
+  // 技能标签颜色映射
+  const skillColors = [
+    'bg-blue-100 text-blue-800',
+    'bg-green-100 text-green-800',
+    'bg-purple-100 text-purple-800',
+    'bg-yellow-100 text-yellow-800',
+    'bg-pink-100 text-pink-800',
+    'bg-indigo-100 text-indigo-800'
+  ];
+
+  const getSkillColor = (index: number) => skillColors[index % skillColors.length];
+
   return (
     <div className="min-h-screen bg-gray-50">
       {/* 头部导航 */}
       <header className="sticky top-0 bg-white shadow-sm border-b border-gray-200 px-4 py-4">
         <div className="flex items-center justify-between">
           <h1 className="text-xl font-bold text-gray-800">银龄人才库</h1>
-          <UserIcon className="w-6 h-6 text-gray-600 cursor-pointer" onClick={() => navigate('/profile')} />
+          <div className="flex items-center space-x-3">
+            <button
+              onClick={() => setShowMockData(!showMockData)}
+              className="px-2 py-1 text-xs bg-blue-100 text-blue-600 rounded-full"
+            >
+              {showMockData ? '演示数据' : '真实数据'}
+            </button>
+            <UserIcon className="w-6 h-6 text-gray-600 cursor-pointer" onClick={() => navigate('/profile')} />
+          </div>
         </div>
       </header>
 
@@ -51,6 +251,28 @@ const SilverTalentsPage: React.FC = () => {
         </div>
       </div>
 
+      {/* 统计卡片 */}
+      <div className="px-4 mb-4">
+        <div className="grid grid-cols-3 gap-3">
+          <div className="bg-white rounded-lg p-3 text-center shadow-sm">
+            <div className="text-2xl font-bold text-blue-600">{talents.length}</div>
+            <div className="text-xs text-gray-600">认证人才</div>
+          </div>
+          <div className="bg-white rounded-lg p-3 text-center shadow-sm">
+            <div className="text-2xl font-bold text-green-600">
+              {talents.reduce((sum, t) => sum + t.totalPoints, 0)}
+            </div>
+            <div className="text-xs text-gray-600">总积分</div>
+          </div>
+          <div className="bg-white rounded-lg p-3 text-center shadow-sm">
+            <div className="text-2xl font-bold text-purple-600">
+              {talents.reduce((sum, t) => sum + t.knowledgeShareCount, 0)}
+            </div>
+            <div className="text-xs text-gray-600">知识分享</div>
+          </div>
+        </div>
+      </div>
+
       {/* 人才列表 */}
       <div className="px-4 pb-20">
         {isLoading ? (
@@ -58,11 +280,11 @@ const SilverTalentsPage: React.FC = () => {
             {[...Array(6)].map((_, i) => (
               <div key={i} className="bg-white rounded-lg shadow-sm p-4">
                 <div className="flex items-center space-x-4">
-                  <div className="w-16 h-16 bg-gray-200 rounded-full"></div>
+                  <div className="w-16 h-16 bg-gray-200 rounded-full animate-pulse"></div>
                   <div className="space-y-2 flex-1">
-                    <div className="h-4 bg-gray-200 rounded w-1/2"></div>
-                    <div className="h-3 bg-gray-200 rounded w-3/4"></div>
-                    <div className="h-3 bg-gray-200 rounded w-1/3"></div>
+                    <div className="h-4 bg-gray-200 rounded w-1/2 animate-pulse"></div>
+                    <div className="h-3 bg-gray-200 rounded w-3/4 animate-pulse"></div>
+                    <div className="h-3 bg-gray-200 rounded w-1/3 animate-pulse"></div>
                   </div>
                 </div>
               </div>
@@ -73,55 +295,117 @@ const SilverTalentsPage: React.FC = () => {
             {talents.map((talent: any) => (
               <div
                 key={talent.id}
-                className="bg-white rounded-lg shadow-sm p-4 cursor-pointer hover:shadow-md transition-shadow"
+                className="bg-white rounded-lg shadow-sm p-4 cursor-pointer hover:shadow-md transition-shadow border-l-4 border-blue-500"
                 onClick={() => navigate(`/silver-talents/${talent.id}`)}
               >
-                <div className="flex items-center space-x-4">
-                  <div className="relative">
+                <div className="flex items-start space-x-4">
+                  {/* 头像区域 */}
+                  <div className="relative flex-shrink-0">
                     <img
-                      src={talent.avatarUrl || '/images/avatar-placeholder.jpg'}
+                      src={talent.avatarUrl || '/images/placeholder.jpg'}
                       alt={talent.realName}
-                      className="w-16 h-16 rounded-full object-cover"
+                      className="w-20 h-20 rounded-full object-cover border-2 border-gray-200"
+                      onError={(e) => {
+                        (e.target as HTMLImageElement).src = '/images/placeholder.jpg';
+                      }}
                     />
                     {talent.certificationStatus === 'CERTIFIED' && (
-                      <div className="absolute -top-1 -right-1 w-5 h-5 bg-green-500 rounded-full flex items-center justify-center text-white text-xs">
+                      <div className="absolute -top-1 -right-1 w-6 h-6 bg-green-500 rounded-full flex items-center justify-center text-white text-xs shadow-lg">
                       </div>
                     )}
                   </div>
                   
-                  <div className="flex-1">
-                    <h3 className="font-bold text-gray-800 mb-1">{talent.realName}</h3>
-                    <p className="text-sm text-gray-600 mb-1">
-                      {talent.age}岁 {talent.organization && `· ${talent.organization}`}
-                    </p>
+                  {/* 信息区域 */}
+                  <div className="flex-1 min-w-0">
+                    {/* 姓名和基础信息 */}
+                    <div className="flex items-center justify-between mb-2">
+                      <h3 className="font-bold text-lg text-gray-800">{talent.realName}</h3>
+                      <span className="text-sm text-gray-500">{talent.nickname}</span>
+                    </div>
                     
-                    <div className="flex flex-wrap gap-1 mb-2">
-                      {talent.personalSkills?.split(',').slice(0, 3).map((skill: string, i: number) => (
-                        <span key={i} className="px-2 py-1 bg-gray-100 text-xs text-gray-700 rounded-full">
+                    <p className="text-sm text-gray-600 mb-2">
+                      <span className="font-semibold">{talent.age}岁</span>
+                      {talent.organization && (
+                        <span className="ml-2 text-gray-500">· {talent.organization}</span>
+                      )}
+                    </p>
+
+                    {/* 个人简介 */}
+                    <p className="text-sm text-gray-700 mb-3 line-clamp-2">
+                      {talent.personalIntro}
+                    </p>
+
+                    {/* 技能标签 */}
+                    <div className="flex flex-wrap gap-1 mb-3">
+                      {talent.personalSkills?.split(',').slice(0, 4).map((skill: string, i: number) => (
+                        <span 
+                          key={i} 
+                          className={`px-2 py-1 text-xs rounded-full ${getSkillColor(i)}`}
+                        >
                           {skill.trim()}
                         </span>
                       ))}
+                      {talent.personalSkills?.split(',').length > 4 && (
+                        <span className="px-2 py-1 text-xs bg-gray-100 text-gray-600 rounded-full">
+                          +{talent.personalSkills.split(',').length - 4}
+                        </span>
+                      )}
                     </div>
 
+                    {/* 统计信息 */}
                     <div className="flex items-center justify-between text-xs text-gray-500">
-                      <div className="flex items-center space-x-4">
-                        <span>浏览 {talent.knowledgeReadCount || 0}</span>
-                        <span>评分 {talent.knowledgeRankingScore || 0}</span>
+                      <div className="flex items-center space-x-3">
+                        <span>📖 {talent.knowledgeReadCount || 0} 阅读</span>
+                        <span>👍 {talent.knowledgeLikeCount || 0} 点赞</span>
+                        <span>⭐ {talent.knowledgeRankingScore || 0} 评分</span>
+                        <span>💰 {talent.totalPoints || 0} 积分</span>
+                      </div>
+                      <div className="flex items-center space-x-2">
+                        {talent.jobSeekingStatus === 'ACTIVELY_SEEKING' && (
+                          <span className="px-2 py-1 bg-green-100 text-green-700 rounded-full text-xs">
+                            求职中
+                          </span>
+                        )}
+                        {talent.jobSeekingStatus === 'OPEN_TO_OPPORTUNITIES' && (
+                          <span className="px-2 py-1 bg-blue-100 text-blue-700 rounded-full text-xs">
+                            开放机会
+                          </span>
+                        )}
+                        <HeartIcon className="w-4 h-4 text-red-400" />
                       </div>
-                      <HeartIcon className="w-4 h-4 text-red-400" />
                     </div>
                   </div>
                 </div>
+
+                {/* 求职需求 */}
+                {talent.jobSeekingRequirements && (
+                  <div className="mt-3 pt-3 border-t border-gray-100">
+                    <p className="text-xs text-gray-600 italic">
+                      💼 {talent.jobSeekingRequirements}
+                    </p>
+                  </div>
+                )}
               </div>
             ))}
           </div>
         ) : (
           <div className="text-center py-12">
-            <p className="text-gray-500 text-lg">暂无银龄人才信息</p>
+            <div className="text-gray-400 mb-4">
+              <UserIcon className="w-16 h-16 mx-auto mb-4" />
+            </div>
+            <p className="text-gray-500 text-lg mb-2">暂无银龄人才信息</p>
+            <p className="text-gray-400 text-sm">尝试调整搜索条件或清除搜索关键词</p>
           </div>
         )}
       </div>
+
+      {/* 底部提示 */}
+      <div className="fixed bottom-20 left-0 right-0 px-4">
+        <div className="bg-blue-500 text-white rounded-lg p-3 text-center text-sm">
+          💡 提示:点击卡片查看人才详情,支持收藏和联系功能
+        </div>
+      </div>
     </div>
   );
 };