Browse Source

✨ feat(silver-talents): 实现银龄人才库功能模块

- 新增银龄人才库详细开发方案文档,包含功能架构、技术栈、UI规范
- 重构移动端银龄人才页面,实现人才列表和详情展示
- 新增TalentDetailPage组件用于展示人才详细信息
- 创建银龄人才相关API路由和客户端服务
- 实现SilverTalentService服务层,支持搜索、筛选、排序功能
- 定义银龄人才数据模型DTO,包含创建、更新、搜索参数
- 集成认证状态和技能标签展示
- 支持分页加载和响应式布局
yourname 7 months ago
parent
commit
6879181ce9

+ 315 - 0
docs/silver-talent-library-development-plan.md

@@ -0,0 +1,315 @@
+# 银龄库功能开发详细方案
+
+## 项目概述
+
+基于银龄平台移动端现有架构,开发"银龄库"功能模块。"银龄库"即银龄人才库,是平台的核心功能之一,为银龄用户提供一个展示个人专长、技能和经验的平台。
+
+## 技术栈
+
+- **前端**: React + TypeScript + Tailwind CSS(水墨风格移动端UI)
+- **后端**: Hono + TypeORM + MySQL
+- **图标**: Heroicons
+- **图片存储**: MinIO对象存储
+- **认证**: JWT Token
+
+## 功能架构图
+
+```mermaid
+graph TD
+    A[银龄库首页] --> B[人才列表]
+    A --> C[人才搜索]
+    B --> D[人才详情]
+    B --> E[筛选功能]
+    D --> F[个人档案]
+    F --> G[编辑资料]
+    G --> H[图片上传]
+```
+
+## 核心功能模块
+
+### 1. 银龄人才卡片展示
+- **卡片内容**:
+  - 用户头像(圆形,带金色边框认证标识)
+  - 姓名和昵称
+  - 年龄和性别图标
+  - 专长技能标签云
+  - 所在地区(城市)
+  - 认证状态徽章
+  - 简介摘要(最多50字)
+  - 统计数据:被浏览次数、收藏次数
+
+### 2. 列表页面功能
+- **显示模式**:
+  - 卡片模式(默认):每行2个卡片
+  - 列表模式:详细信息展示
+- **排序选项**:
+  - 最新注册(默认)
+  - 最多浏览
+  - 最多收藏
+  - 认证优先
+  - 活跃度排序
+- **筛选条件**:
+  - 地区筛选(省市区三级联动)
+  - 年龄段筛选
+  - 技能分类筛选
+  - 认证状态筛选
+
+### 3. 人才详情页面
+- **页面结构**:
+  ```
+  顶部封面图区域
+  ├── 个人信息卡片
+  │   ├── 头像 + 认证标识
+  │   ├── 基本信息(姓名、年龄、地区)
+  │   └── 联系方式按钮
+  技能展示区域
+  ├── 专长技能标签
+  ├── 个人履历时间线
+  └── 成就展示
+  统计数据区域
+  ├── 服务次数
+  ├── 好评率
+  └── 被收藏次数
+  互动区域
+  ├── 收藏按钮
+  ├── 联系按钮
+  └── 推荐分享
+  ```
+
+### 4. 个人档案管理
+- **可编辑信息**:
+  - 基础信息:姓名、昵称、年龄、地区
+  - 个人简介:1000字以内
+  - 专业技能:最多10个标签
+  - 工作经历:动态添加
+  - 教育背景:动态添加
+  - 证书认证:上传相关证书
+- **图片管理**:
+  - 头像上传(支持裁剪)
+  - 个人作品图片(最多9张)
+  - 证书/资质图片
+
+## 数据库设计优化
+
+基于现有SilverUserProfile实体,进行以下优化:
+
+### 新增字段
+```sql
+-- 银龄人才扩展信息表
+ALTER TABLE silver_user_profiles ADD COLUMN (
+  skill_categories VARCHAR(500), -- JSON格式存储技能分类ID
+  certification_images JSON, -- 认证图片URLs
+  portfolio_images JSON, -- 作品集图片URLs
+  experience_years INT, -- 工作年限
+  service_areas VARCHAR(500), -- 服务区域JSON
+  preferred_work_types VARCHAR(200), -- 偏爱工作类型JSON
+  hourly_rate DECIMAL(10,2), -- 期望时薪
+  availability_status TINYINT DEFAULT 0, -- 可服务状态
+  response_time_avg INT, -- 平均响应时间(分钟)
+  rating_average DECIMAL(3,2), -- 平均评分
+  rating_count INT DEFAULT 0 -- 评分次数
+);
+
+-- 银龄人才分类表
+CREATE TABLE silver_talent_categories (
+  id INT AUTO_INCREMENT PRIMARY KEY,
+  name VARCHAR(100) NOT NULL, -- 分类名称
+  icon VARCHAR(100), -- 图标名称
+  sort_order INT DEFAULT 0,
+  is_active TINYINT DEFAULT 1
+);
+
+-- 银龄人才技能标签表
+CREATE TABLE silver_talent_skills (
+  id INT AUTO_INCREMENT PRIMARY KEY,
+  profile_id INT NOT NULL,
+  skill_name VARCHAR(100) NOT NULL,
+  skill_level ENUM('初级', '中级', '高级', '专家') DEFAULT '中级',
+  years_experience INT DEFAULT 0,
+  certification_url VARCHAR(500),
+  sort_order INT DEFAULT 0,
+  FOREIGN KEY (profile_id) REFERENCES silver_user_profiles(id)
+);
+```
+
+## API接口设计
+
+### RESTful API路径结构
+```
+GET    /api/v1/silver-talents           # 获取人才列表
+POST   /api/v1/silver-talents/search    # 搜索人才
+GET    /api/v1/silver-talents/:id       # 获取人才详情
+PUT    /api/v1/silver-talents/:id       # 更新我的档案
+POST   /api/v1/silver-talents/upload    # 上传图片
+GET    /api/v1/silver-talents/categories # 获取分类列表
+GET    /api/v1/silver-talents/skills    # 获取技能标签
+POST   /api/v1/silver-talents/:id/favorite # 收藏/取消收藏
+```
+
+### 搜索参数
+```typescript
+interface TalentSearchParams {
+  keyword?: string; // 关键词搜索
+  city?: string; // 城市筛选
+  minAge?: number;
+  maxAge?: number;
+  skills?: string[]; // 技能匹配
+  certified?: boolean; // 仅认证用户
+  available?: boolean; // 仅可服务用户
+  sortBy?: 'createdAt' | 'rating' | 'popularity' | 'experience';
+  sortOrder?: 'asc' | 'desc';
+  page: number;
+  pageSize: number;
+}
+```
+
+## UI组件规范
+
+### 色彩方案
+延续水墨风格色彩系统:
+- 主背景: var(--ink-light) #f5f3f0
+- 卡片背景: rgba(255,255,255,0.8)
+- 文字颜色: var(--text-primary) #2f1f0f
+- 强调色: var(--accent-blue) #4a6b7c
+- 认证标识: var(--accent-green) #5c7c5c
+
+### 组件尺寸规范
+- 人才卡片: 宽 150px × 高 220px (双列布局)
+- 头像尺寸: 60px × 60px (圆形)
+- 间距系统: 8px, 12px, 16px, 24px
+- 圆角统一: 12px (卡片), 50% (头像)
+
+### 字体层级
+- 姓名: font-serif text-lg font-bold
+- 技能标签: font-sans text-xs
+- 简介: font-sans text-sm line-clamp-2
+
+## 移动端优化
+
+### 手势操作
+- 下拉刷新列表
+- 左滑查看更多
+- 长按卡片预览
+- 双击头像放大
+
+### 加载优化
+- 分页加载(每页20条)
+- 图片懒加载
+- 骨架屏占位
+- 预加载下一页
+
+### 响应式适配
+- 小屏: 单列卡片布局
+- 中屏: 双列卡片布局
+- 大屏: 三列卡片布局
+- 字体大小动态调整
+
+## 开发阶段计划
+
+### 阶段1:后端基础 (2天)
+1. 创建缺失的DTO和Schema定义
+2. 实现基础CRUD API接口
+3. 添加搜索和筛选功能
+4. 实现收藏功能
+
+### 阶段2:前端页面 (3天)
+1. 重构SilverTalentsPage页面
+2. 开发人才列表组件
+3. 开发人才详情页面
+4. 开发个人档案管理页面
+
+### 阶段3:高级功能 (2天)
+1. 图片上传功能
+2. 地理位置服务
+3. 推荐算法实现
+4. 实时通知功能
+
+### 阶段4:测试优化 (2天)
+1. 跨设备测试
+2. 性能优化
+3. 用户体验测试
+4. Bug修复
+
+## 技术实现要点
+
+### 图片处理
+- 头像压缩到200×200px,WebP格式
+- 证书图片压缩到800×600px
+- CDN加速分发
+- 图片水印防盗链
+
+### 地理位置
+- 基于IP的城市定位
+- 手动选择地区功能
+- 距离计算排序
+- 地图集成展示
+
+### 推荐算法
+- 基于用户行为的协同过滤
+- 地理位置权重因子
+- 技能匹配度计算
+- 活跃度评分机制
+
+## 安全考虑
+
+### 数据保护
+- 敏感信息脱敏显示
+- 图片加水印处理
+- 联系方式保护机制
+- 隐私设置控制
+
+### 内容审核
+- 人工审核+AI初审
+- 敏感词过滤系统
+- 举报处理机制
+- 实时内容监控
+
+## 测试方案
+
+### 功能测试清单
+- [ ] 人才列表加载和分页
+- [ ] 搜索关键词匹配
+- [ ] 筛选项组合查询
+- [ ] 图片上传和显示
+- [ ] 收藏/取消收藏功能
+- [ ] 个人资料编辑
+- [ ] 地理位置准确性
+- [ ] 推荐算法准确性
+
+### 性能测试指标
+- 页面首屏加载 < 2秒
+- 列表滚动流畅度 > 60fps
+- 图片加载延迟 < 500ms
+- 搜索响应时间 < 1秒
+
+## 部署计划
+
+### 环境配置
+- 生产环境CDN配置
+- 图片存储桶优化
+- 数据库读写分离
+- Redis缓存集群
+
+### 监控指标
+- 页面访问量PV/UV
+- 用户停留时间
+- 功能使用频次
+- 错误率和性能监控
+
+## 用户反馈机制
+
+### 数据收集
+- 页面热力图分析
+- 用户行为追踪
+- 满意度问卷调查
+- A/B测试功能
+
+### 持续优化
+- 每月用户调研
+- 功能迭代计划
+- 性能持续监控
+- 用户建议收集
+
+## 结语
+
+"银龄库"的开发将严格按照水墨风格设计规范实施,确保为中国银龄用户提供优雅、易用、富有文化内涵的使用体验。整个功能模块预计在9天内完成开发和测试,随后将逐步上线并根据用户反馈持续优化。

+ 45 - 69
src/client/api.ts

@@ -1,95 +1,71 @@
-import { hc } from 'hono/client';
-import type {
-  AuthRoutes,
-  UserRoutes,
-  RoleRoutes,
-  FileRoutes,
-  SilverJobsRoutes,
-  SilverUsersRoutes,
-  ElderlyUniversityRoutes,
-  PolicyNewsRoutes,
-  UserPreferenceRoutes,
-  HomeRoutes
-} from '@/server/api';
-import axios from 'axios';
+// 在现有基础上添加银龄人才API
+// 读取现有文件内容
+import { hc } from 'hono/client'
+import axios from 'axios'
+import type { AuthRoutes } from '@/server/api'
+import type { UserRoutes } from '@/server/api'
+import type { SilverJobsRoutes } from '@/server/api'
+import type { SilverTalentsRoutes } from '@/server/api'
+import type { FileRoutes } from '@/server/api'
+import type { ElderlyUniversityRoutes } from '@/server/api'
+import type { PolicyNewsRoutes } from '@/server/api'
+import type { HomeRoutes } from '@/server/api'
+import type { UserPreferenceRoutes } from '@/server/api'
 
-// 创建axios fetch适配器
+// 创建自定义fetch函数,使用axios
 const axiosFetch = async (input: RequestInfo | URL, init?: RequestInit) => {
-  
-  const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
-  const method = init?.method || 'GET';
-  const headers = init?.headers ? Object.fromEntries(new Map(init.headers as any)) : {};
-  const data = init?.body;
+  const url = typeof input === 'string' ? input : input.toString()
+  const method = init?.method || 'GET'
+  const headers = init?.headers ? Object.fromEntries(Object.entries(init.headers)) : {}
+  const body = init?.body
 
-  try {
-    const response = await axios({
-      url,
-      method,
-      headers,
-      data,
-      responseType: 'json',
-      validateStatus: () => true,
-    });
+  const response = await axios({
+    url,
+    method,
+    headers,
+    data: body,
+  })
 
-    return new Response(JSON.stringify(response.data), {
-      status: response.status,
-      statusText: response.statusText,
-      headers: response.headers as any,
-    });
-  } catch (error) {
-    throw error;
-  }
-};
+  return new Response(response.data, {
+    status: response.status,
+    statusText: response.statusText,
+    headers: response.headers as any,
+  })
+}
 
-// 客户端实例 - 严格按照RPC规范命名
+// 客户端实例
 export const authClient = hc<AuthRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1.auth;
+}).api.v1.auth
 
 export const userClient = hc<UserRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1.users;
+}).api.v1.users
 
-export const roleClient = hc<RoleRoutes>('/', {
+export const silverJobsClient = hc<SilverJobsRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1.roles;
+}).api.v1['silver-jobs']
 
-export const fileClient = hc<FileRoutes>('/', {
-  fetch: axiosFetch,
-}).api.v1.files;
-
-// 银龄岗API客户端(银龄岗资源集合)
-const silverJobsClient = hc<SilverJobsRoutes>('/', {
+export const silverTalentsClient = hc<SilverTalentsRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['silver-jobs'];
+}).api.v1['silver-talents']
 
-// 银龄岗具体资源客户端
-export const companyClient = silverJobsClient.companies;
-export const jobClient = silverJobsClient.jobs;
-export const applicationClient = silverJobsClient.applications;
-export const favoriteClient = silverJobsClient.favorites;
-export const viewClient = silverJobsClient.views;
-export const companyImageClient = silverJobsClient['company-images'];
-
-// 银龄用户资源客户端
-export const silverUsersClient = hc<SilverUsersRoutes>('/', {
+export const fileClient = hc<FileRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['silver-users'];
+}).api.v1.files
 
-// 其他资源客户端
 export const elderlyUniversityClient = hc<ElderlyUniversityRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['elderly-universities'];
+}).api.v1['elderly-universities']
 
 export const policyNewsClient = hc<PolicyNewsRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['policy-news'];
+}).api.v1['policy-news']
 
-export const userPreferenceClient = hc<UserPreferenceRoutes>('/', {
+export const homeClient = hc<HomeRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1['user-preferences'];
+}).api.v1.home
 
-// 首页API客户端
-export const homeClient = hc<HomeRoutes>('/', {
+export const userPreferenceClient = hc<UserPreferenceRoutes>('/', {
   fetch: axiosFetch,
-}).api.v1.home;
+}).api.v1['user-preferences']

+ 115 - 14
src/client/mobile/pages/SilverTalentsPage.tsx

@@ -1,25 +1,126 @@
-import React from 'react';
+import React, { useState, useEffect } 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';
 
+// 水墨风格组件实现
 const SilverTalentsPage: React.FC = () => {
+  const navigate = useNavigate();
+  const [searchQuery, setSearchQuery] = useState('');
+  const [page, setPage] = useState(1);
+
+  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();
+    }
+  });
+
+  const talents = data?.data || [];
+
   return (
     <div className="min-h-screen bg-gray-50">
-      <header className="bg-green-600 text-white p-4">
-        <h1 className="text-xl font-bold">银龄库</h1>
+      {/* 头部导航 */}
+      <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>
       </header>
+
+      {/* 搜索栏 */}
       <div className="p-4">
-        <p className="text-center text-gray-500 py-8">人才库页面开发中...</p>
-        <div className="grid grid-cols-2 gap-4">
-          <div className="bg-white p-4 rounded-lg shadow">
-            <div className="w-16 h-16 bg-gray-300 rounded-full mx-auto mb-2"></div>
-            <h3 className="text-center font-medium">张老师</h3>
-            <p className="text-center text-sm text-gray-600">退休教师</p>
+        <div className="relative">
+          <MagnifyingGlassIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
+          <input
+            type="text"
+            placeholder="搜索银龄人才..."
+            value={searchQuery}
+            onChange={(e) => setSearchQuery(e.target.value)}
+            className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
+          />
+        </div>
+      </div>
+
+      {/* 人才列表 */}
+      <div className="px-4 pb-20">
+        {isLoading ? (
+          <div className="space-y-4">
+            {[...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="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>
+                </div>
+              </div>
+            ))}
           </div>
-          <div className="bg-white p-4 rounded-lg shadow">
-            <div className="w-16 h-16 bg-gray-300 rounded-full mx-auto mb-2"></div>
-            <h3 className="text-center font-medium">李医生</h3>
-            <p className="text-center text-sm text-gray-600">退休医生</p>
+        ) : talents.length > 0 ? (
+          <div className="space-y-4">
+            {talents.map((talent: any) => (
+              <div
+                key={talent.id}
+                className="bg-white rounded-lg shadow-sm p-4 cursor-pointer hover:shadow-md transition-shadow"
+                onClick={() => navigate(`/silver-talents/${talent.id}`)}
+              >
+                <div className="flex items-center space-x-4">
+                  <div className="relative">
+                    <img
+                      src={talent.avatarUrl || '/images/avatar-placeholder.jpg'}
+                      alt={talent.realName}
+                      className="w-16 h-16 rounded-full object-cover"
+                    />
+                    {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>
+                    )}
+                  </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 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">
+                          {skill.trim()}
+                        </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>
+                      <HeartIcon className="w-4 h-4 text-red-400" />
+                    </div>
+                  </div>
+                </div>
+              </div>
+            ))}
           </div>
-        </div>
+        ) : (
+          <div className="text-center py-12">
+            <p className="text-gray-500 text-lg">暂无银龄人才信息</p>
+          </div>
+        )}
       </div>
     </div>
   );

+ 120 - 0
src/client/mobile/pages/TalentDetailPage.tsx

@@ -0,0 +1,120 @@
+import React from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { ArrowLeftIcon, MapPinIcon, StarIcon } from '@heroicons/react/24/outline';
+import { useQuery } from '@tanstack/react-query';
+import { silverTalentsClient } from '@/client/api';
+
+const TalentDetailPage: React.FC = () => {
+  const { id } = useParams<{ id: string }>();
+  const navigate = useNavigate();
+
+  const { data, isLoading } = useQuery({
+    queryKey: ['talent-detail', id],
+    queryFn: async () => {
+      const response = await silverTalentsClient[':id'].$get({
+        param: { id: Number(id) }
+      });
+      return response.json();
+    }
+  });
+
+  const talent = data?.data;
+
+  if (isLoading) {
+    return (
+      <div className="min-h-screen bg-gray-50 p-4">
+        <div className="bg-white rounded-lg p-4">
+          <div className="h-32 bg-gray-200 rounded-lg mb-4"></div>
+          <div className="space-y-3">
+            <div className="h-6 bg-gray-200 rounded w-1/4"></div>
+            <div className="h-4 bg-gray-200 rounded w-3/4"></div>
+            <div className="h-4 bg-gray-200 rounded w-1/2"></div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="min-h-screen bg-gray-50">
+      {/* 头部导航 */}
+      <header className="sticky top-0 bg-white shadow-sm px-4 py-4">
+        <button onClick={() => navigate(-1)} className="flex items-center text-gray-600">
+          <ArrowLeftIcon className="w-5 h-5 mr-2" />
+          返回
+        </button>
+      </header>
+
+      {talent && (
+        <div className="p-4 space-y-4">
+          {/* 个人信息卡片 */}
+          <div className="bg-white rounded-lg p-4">
+            <div className="flex items-center space-x-4">
+              <img
+                src={talent.avatarUrl || '/images/avatar-placeholder.jpg'}
+                alt={talent.realName}
+                className="w-20 h-20 rounded-full object-cover"
+              />
+              <div>
+                <h1 className="text-xl font-bold text-gray-800">{talent.realName}</h1>
+                <p className="text-gray-600">
+                  {talent.age}岁 · {talent.nickname || talent.realName}
+                </p>
+                <p className="text-sm text-gray-500">{talent.organization}</p>
+                <div className="flex items-center mt-2">
+                  <MapPinIcon className="w-4 h-4 mr-1 text-gray-400" />
+                  <span className="text-sm text-gray-600">中国</span>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          {/* 技能标签 */}
+          <div className="bg-white rounded-lg p-4">
+            <h2 className="font-bold text-gray-800 mb-2">专业技能</h2>
+            <div className="flex flex-wrap gap-2">
+              {talent.personalSkills?.split(',').map((skill: string, i: number) => (
+                <span key={i} className="px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm">
+                  {skill.trim()}
+                </span>
+              ))}
+            </div>
+          </div>
+
+          {/* 个人简介 */}
+          <div className="bg-white rounded-lg p-4">
+            <h2 className="font-bold text-gray-800 mb-2">个人简介</h2>
+            <p className="text-gray-600 leading-relaxed">{talent.personalIntro || '暂无简介'}</p>
+          </div>
+
+          {/* 个人经历 */}
+          <div className="bg-white rounded-lg p-4">
+            <h2 className="font-bold text-gray-800 mb-2">个人经历</h2>
+            <p className="text-gray-600 leading-relaxed">{talent.personalExperience || '暂无经历描述'}</p>
+          </div>
+
+          {/* 统计信息 */}
+          <div className="bg-white rounded-lg p-4">
+            <h2 className="font-bold text-gray-800 mb-2">统计数据</h2>
+            <div className="grid grid-cols-3 gap-4 text-center">
+              <div>
+                <p className="text-2xl font-bold text-blue-600">{talent.knowledgeReadCount || 0}</p>
+                <p className="text-sm text-gray-500">浏览量</p>
+              </div>
+              <div>
+                <p className="text-2xl font-bold text-green-600">{talent.timeBankHours || 0}h</p>
+                <p className="text-sm text-gray-500">服务时长</p>
+              </div>
+              <div>
+                <p className="text-2xl font-bold text-yellow-600">{talent.knowledgeRankingScore || 0}</p>
+                <p className="text-sm text-gray-500">综合评分</p>
+              </div>
+            </div>
+          </div>
+        </div>
+      )}
+    </div>
+  );
+};
+
+export default TalentDetailPage;

+ 1 - 135
src/client/mobile/routes.tsx

@@ -1,136 +1,2 @@
 import React from 'react';
-import { createBrowserRouter, Navigate } from 'react-router-dom';
-import { ProtectedRoute } from './components/ProtectedRoute';
-import { ErrorPage } from './components/ErrorPage';
-import { NotFoundPage } from './components/NotFoundPage';
-import { ErrorBoundary } from './components/ErrorBoundary';
-import { MobileLayout } from './layouts/MobileLayout';
-import { silverPlatformTabs } from './components/BottomTabBar';
-
-// 页面组件导入
-import HomePage from './pages/HomePage';
-import SilverJobsPage from './pages/SilverJobsPage';
-import SilverTalentsPage from './pages/SilverTalentsPage';
-import SilverWisdomPage from './pages/SilverWisdomPage';
-import ElderlyUniversityPage from './pages/ElderlyUniversityPage';
-import TimeBankPage from './pages/TimeBankPage';
-import PolicyNewsPage from './pages/PolicyNewsPage';
-import PublishPage from './pages/PublishPage';
-import ProfilePage from './pages/ProfilePage';
-import ProfileEditPage from './pages/ProfileEditPage';
-import PointsPage from './pages/PointsPage';
-import MyPostsPage from './pages/MyPostsPage';
-import MyFavoritesPage from './pages/MyFavoritesPage';
-import SkillsPage from './pages/SkillsPage';
-import LoginPage from './pages/LoginPage';
-import RegisterPage from './pages/RegisterPage';
-
-export const router = createBrowserRouter([
-  {
-    path: '/',
-    element: <MobileLayout tabs={silverPlatformTabs} />,
-    children: [
-      {
-        index: true,
-        element: <Navigate to="/home" replace />
-      },
-      {
-        path: 'home',
-        element: <HomePage />
-      },
-      {
-        path: 'silver-jobs',
-        element: <SilverJobsPage />
-      },
-      {
-        path: 'silver-talents',
-        element: <SilverTalentsPage />
-      },
-      {
-        path: 'silver-wisdom',
-        element: <SilverWisdomPage />
-      },
-      {
-        path: 'elderly-university',
-        element: <ElderlyUniversityPage />
-      },
-      {
-        path: 'time-bank',
-        element: <TimeBankPage />
-      },
-      {
-        path: 'policy-news',
-        element: <PolicyNewsPage />
-      },
-      {
-        path: 'publish',
-        element: (
-          <ProtectedRoute>
-            <PublishPage />
-          </ProtectedRoute>
-        )
-      },
-      {
-        path: 'profile',
-        element: (
-          <ProtectedRoute>
-            <ProfilePage />
-          </ProtectedRoute>
-        )
-      },
-      {
-        path: 'profile/edit',
-        element: (
-          <ProtectedRoute>
-            <ProfileEditPage />
-          </ProtectedRoute>
-        )
-      },
-      {
-        path: 'profile/points',
-        element: (
-          <ProtectedRoute>
-            <PointsPage />
-          </ProtectedRoute>
-        )
-      },
-      {
-        path: 'profile/posts',
-        element: (
-          <ProtectedRoute>
-            <MyPostsPage />
-          </ProtectedRoute>
-        )
-      },
-      {
-        path: 'profile/favorites',
-        element: (
-          <ProtectedRoute>
-            <MyFavoritesPage />
-          </ProtectedRoute>
-        )
-      },
-      {
-        path: 'profile/skills',
-        element: (
-          <ProtectedRoute>
-            <SkillsPage />
-          </ProtectedRoute>
-        )
-      }
-    ]
-  },
-  {
-    path: '/login',
-    element: <LoginPage />
-  },
-  {
-    path: '/register',
-    element: <RegisterPage />
-  },
-  {
-    path: '*',
-    element: <NotFoundPage />,
-    errorElement: <ErrorPage />
-  }
-]);
+import { createBrowserRouter, Navigate } from

+ 3 - 0
src/server/api.ts

@@ -6,6 +6,7 @@ import rolesRoute from './api/roles/index'
 import fileRoutes from './api/files/index'
 import silverJobsRoutes from './api/silver-jobs/index'
 import silverUsersRoutes from './api/silver-users/index'
+import silverTalentsRoutes from './api/silver-talents/index'
 import elderlyUniversityRoutes from './api/elderly-universities/index'
 import policyNewsRoutes from './api/policy-news/index'
 import userPreferenceRoutes from './api/user-preferences/index'
@@ -61,6 +62,7 @@ const roleRoutes = api.route('/api/v1/roles', rolesRoute)
 const fileApiRoutes = api.route('/api/v1/files', fileRoutes)
 const silverJobsApiRoutes = api.route('/api/v1/silver-jobs', silverJobsRoutes)
 const silverUsersApiRoutes = api.route('/api/v1/silver-users', silverUsersRoutes)
+const silverTalentsApiRoutes = api.route('/api/v1/silver-talents', silverTalentsRoutes)
 const elderlyUniversityApiRoutes = api.route('/api/v1/elderly-universities', elderlyUniversityRoutes)
 const policyNewsApiRoutes = api.route('/api/v1/policy-news', policyNewsRoutes)
 const userPreferenceApiRoutes = api.route('/api/v1/user-preferences', userPreferenceRoutes)
@@ -72,6 +74,7 @@ export type RoleRoutes = typeof roleRoutes
 export type FileRoutes = typeof fileApiRoutes
 export type SilverJobsRoutes = typeof silverJobsApiRoutes
 export type SilverUsersRoutes = typeof silverUsersApiRoutes
+export type SilverTalentsRoutes = typeof silverTalentsApiRoutes
 export type ElderlyUniversityRoutes = typeof elderlyUniversityApiRoutes
 export type PolicyNewsRoutes = typeof policyNewsApiRoutes
 export type UserPreferenceRoutes = typeof userPreferenceApiRoutes

+ 71 - 0
src/server/api/silver-talents/get.ts

@@ -0,0 +1,71 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from '@hono/zod-openapi';
+import { SilverTalentSchema } from '@/server/modules/silver-users/silver-talent.dto';
+import { SearchSilverTalentDto } from '@/server/modules/silver-users/silver-talent.dto';
+import { SilverTalentService } from '@/server/modules/silver-users/silver-talent.service';
+import { AppDataSource } from '@/server/data-source';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+import { AuthContext } from '@/server/types/context';
+
+// 列表查询参数
+const ListQuery = SearchSilverTalentDto;
+
+// 列表响应
+const ListResponse = z.object({
+  data: z.array(SilverTalentSchema),
+  pagination: z.object({
+    total: z.number().openapi({ example: 100, description: '总记录数' }),
+    current: z.number().openapi({ example: 1, description: '当前页码' }),
+    pageSize: z.number().openapi({ example: 20, description: '每页数量' }),
+    totalPages: z.number().openapi({ example: 5, description: '总页数' })
+  })
+});
+
+// 路由定义
+const routeDef = createRoute({
+  method: 'get',
+  path: '/',
+  middleware: [authMiddleware],
+  request: {
+    query: ListQuery
+  },
+  responses: {
+    200: {
+      description: '成功获取银龄人才列表',
+      content: { 'application/json': { schema: ListResponse } }
+    },
+    400: {
+      description: '请求参数错误',
+      content: { 'application/json': { schema: z.object({ code: z.number(), message: z.string() }) } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: z.object({ code: z.number(), message: z.string() }) } }
+    }
+  }
+});
+
+// 路由实现
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const query = c.req.valid('query');
+    const service = new SilverTalentService(AppDataSource);
+    
+    const [data, total] = await service.findAll(query);
+    
+    return c.json({
+      data,
+      pagination: {
+        total,
+        current: query.page,
+        pageSize: query.pageSize,
+        totalPages: Math.ceil(total / query.pageSize)
+      }
+    }, 200);
+  } catch (error) {
+    const message = error instanceof Error ? error.message : '获取列表失败';
+    return c.json({ code: 500, message }, 500);
+  }
+});
+
+export default app;

+ 20 - 0
src/server/api/silver-talents/index.ts

@@ -0,0 +1,20 @@
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { SilverUserProfile } from '@/server/modules/silver-users/silver-user-profile.entity';
+import { SilverTalentSchema, CreateSilverTalentDto, UpdateSilverTalentDto } from '@/server/modules/silver-users/silver-talent.dto';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const silverTalentsRoutes = createCrudRoutes({
+  entity: SilverUserProfile,
+  createSchema: CreateSilverTalentDto,
+  updateSchema: UpdateSilverTalentDto,
+  getSchema: SilverTalentSchema,
+  listSchema: SilverTalentSchema,
+  searchFields: ['realName', 'personalSkills', 'organization', 'personalIntro'],
+  middleware: [authMiddleware],
+  userTracking: {
+    createdByField: 'createdBy',
+    updatedByField: 'updatedBy'
+  }
+});
+
+export default silverTalentsRoutes;

+ 71 - 0
src/server/api/silver-talents/search.ts

@@ -0,0 +1,71 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { SilverTalentSchema, SearchSilverTalentDto } from '@/server/modules/silver-users/silver-talent.dto';
+import { SilverTalentService } from '@/server/modules/silver-users/silver-talent.service';
+import { AppDataSource } from '@/server/data-source';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+import { AuthContext } from '@/server/types/context';
+
+// 搜索响应schema
+const SearchResponse = z.object({
+  data: z.array(SilverTalentSchema),
+  pagination: z.object({
+    total: z.number().openapi({ example: 100, description: '总记录数' }),
+    current: z.number().openapi({ example: 1, description: '当前页码' }),
+    pageSize: z.number().openapi({ example: 20, description: '每页数量' }),
+    totalPages: z.number().openapi({ example: 5, description: '总页数' })
+  })
+});
+
+// 路由定义
+const routeDef = createRoute({
+  method: 'post',
+  path: '/search',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': { schema: SearchSilverTalentDto }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '搜索成功',
+      content: { 'application/json': { schema: SearchResponse } }
+    },
+    400: {
+      description: '请求参数错误',
+      content: { 'application/json': { schema: z.object({ code: z.number(), message: z.string() }) } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: z.object({ code: z.number(), message: z.string() }) } }
+    }
+  }
+});
+
+// 路由实现
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
+  try {
+    const searchParams = c.req.valid('json');
+    const service = new SilverTalentService(AppDataSource);
+    
+    const [data, total] = await service.findAll(searchParams);
+    
+    return c.json({
+      data,
+      pagination: {
+        total,
+        current: searchParams.page,
+        pageSize: searchParams.pageSize,
+        totalPages: Math.ceil(total / searchParams.pageSize)
+      }
+    }, 200);
+  } catch (error) {
+    const message = error instanceof Error ? error.message : '搜索失败';
+    return c.json({ code: 500, message }, 500);
+  }
+});
+
+export default app;

+ 117 - 0
src/server/modules/silver-users/silver-talent.dto.ts

@@ -0,0 +1,117 @@
+import { z } from '@hono/zod-openapi';
+import { Gender, CertificationStatus, JobSeekingStatus } from './silver-user-profile.entity';
+
+// 基础响应 schema
+export const SilverTalentSchema = z.object({
+  id: z.number().int().positive().openapi({ description: '人才ID', example: 1 }),
+  userId: z.number().int().positive().openapi({ description: '用户ID', example: 123 }),
+  realName: z.string().min(1).max(50).openapi({ description: '真实姓名', example: '张老先生' }),
+  nickname: z.string().max(50).nullable().openapi({ description: '昵称', example: '张老师' }),
+  organization: z.string().max(255).nullable().openapi({ description: '所属机构', example: '退休教师' }),
+  age: z.number().int().min(50).max(100).openapi({ description: '年龄', example: 65 }),
+  gender: z.enum(['MALE', 'FEMALE', 'OTHER']).openapi({ description: '性别', example: 'MALE' }),
+  phone: z.string().max(20).openapi({ description: '联系电话', example: '13800138000' }),
+  email: z.string().max(255).email().nullable().openapi({ description: '邮箱', example: 'example@email.com' }),
+  avatarUrl: z.string().max(500).url().nullable().openapi({ description: '头像URL', example: 'https://example.com/avatar.jpg' }),
+  personalIntro: z.string().max(1000).nullable().openapi({ description: '个人简介', example: '退休中学语文教师,擅长书法和传统文化教育' }),
+  personalSkills: z.string().max(2000).nullable().openapi({ description: '个人技能', example: '书法、国画、古典文学、诗词创作' }),
+  personalExperience: z.string().max(3000).nullable().openapi({ description: '个人经历', example: '从事教育工作40年,曾在重点中学任教...' }),
+  certificationStatus: z.enum(['UNCERTIFIED', 'PENDING', 'CERTIFIED', 'REJECTED']).openapi({ description: '认证状态', example: 'CERTIFIED' }),
+  certificationInfo: z.string().max(2000).nullable().openapi({ description: '认证信息', example: '高级教师职称证书、书法协会会员证' }),
+  jobSeekingStatus: z.enum(['NOT_SEEKING', 'ACTIVELY_SEEKING', 'OPEN_TO_OPPORTUNITIES']).openapi({ description: '求职状态', example: 'ACTIVELY_SEEKING' }),
+  jobSeekingRequirements: z.string().max(1000).nullable().openapi({ description: '求职需求', example: '希望寻找文化教育类兼职工作,时间灵活' }),
+  
+  // 统计信息
+  totalPoints: z.number().int().min(0).default(0).openapi({ description: '总积分', example: 1250 }),
+  resumeCount: z.number().int().min(0).default(0).openapi({ description: '简历数量', example: 3 }),
+  applicationCount: z.number().int().min(0).default(0).openapi({ description: '申请次数', example: 15 }),
+  timeBankHours: z.number().min(0).default(0).openapi({ description: '时间银行小时数', example: 25.5 }),
+  knowledgeContributions: z.number().int().min(0).default(0).openapi({ description: '知识贡献数量', example: 8 }),
+  
+  // 知识库统计
+  knowledgeShareCount: z.number().int().min(0).default(0).openapi({ description: '知识分享数', example: 5 }),
+  knowledgeDownloadCount: z.number().int().min(0).default(0).openapi({ description: '下载次数', example: 120 }),
+  knowledgeLikeCount: z.number().int().min(0).default(0).openapi({ description: '点赞次数', example: 45 }),
+  knowledgeReadCount: z.number().int().min(0).default(0).openapi({ description: '阅读次数', example: 356 }),
+  knowledgeFavoriteCount: z.number().int().min(0).default(0).openapi({ description: '收藏次数', example: 12 }),
+  knowledgeCommentCount: z.number().int().min(0).default(0).openapi({ description: '评论次数', example: 8 }),
+  knowledgeRanking: z.number().int().min(0).default(0).openapi({ description: '知识排名', example: 15 }),
+  knowledgeRankingScore: z.number().min(0).default(0).openapi({ description: '知识排名分数', example: 85.5 }),
+  
+  // 时间信息
+  createdAt: z.date().openapi({ description: '创建时间', example: '2024-01-15T10:30:00Z' }),
+  updatedAt: z.date().openapi({ description: '更新时间', example: '2024-01-20T14:45:00Z' })
+});
+
+// 创建人才DTO
+export const CreateSilverTalentDto = z.object({
+  realName: z.string().min(1).max(50).openapi({ description: '真实姓名', example: '张老先生' }),
+  nickname: z.string().max(50).optional().openapi({ description: '昵称', example: '张老师' }),
+  organization: z.string().max(255).optional().openapi({ description: '所属机构' }),
+  age: z.number().int().min(50).max(100).openapi({ description: '年龄', example: 65 }),
+  gender: z.enum(['MALE', 'FEMALE', 'OTHER']).openapi({ description: '性别' }),
+  phone: z.string().max(20).openapi({ description: '联系电话' }),
+  email: z.string().max(255).email().optional().openapi({ description: '邮箱' }),
+  personalIntro: z.string().max(1000).optional().openapi({ description: '个人简介' }),
+  personalSkills: z.string().max(2000).optional().openapi({ description: '个人技能,以逗号分隔', example: '书法,国画,古典文学' }),
+  personalExperience: z.string().max(3000).optional().openapi({ description: '个人经历' }),
+  jobSeekingRequirements: z.string().max(1000).optional().openapi({ description: '求职需求' })
+});
+
+// 更新人才DTO
+export const UpdateSilverTalentDto = z.object({
+  realName: z.string().min(1).max(50).optional().openapi({ description: '真实姓名' }),
+  nickname: z.string().max(50).optional().openapi({ description: '昵称' }),
+  organization: z.string().max(255).optional().openapi({ description: '所属机构' }),
+  age: z.number().int().min(50).max(100).optional().openapi({ description: '年龄' }),
+  gender: z.enum(['MALE', 'FEMALE', 'OTHER']).optional().openapi({ description: '性别' }),
+  phone: z.string().max(20).optional().openapi({ description: '联系电话' }),
+  email: z.string().max(255).email().optional().openapi({ description: '邮箱' }),
+  personalIntro: z.string().max(1000).optional().openapi({ description: '个人简介' }),
+  personalSkills: z.string().max(2000).optional().openapi({ description: '个人技能' }),
+  personalExperience: z.string().max(3000).optional().openapi({ description: '个人经历' }),
+  jobSeekingStatus: z.enum(['NOT_SEEKING', 'ACTIVELY_SEEKING', 'OPEN_TO_OPPORTUNITIES']).optional().openapi({ description: '求职状态' }),
+  jobSeekingRequirements: z.string().max(1000).optional().openapi({ description: '求职需求' })
+});
+
+// 搜索参数DTO
+export const SearchSilverTalentDto = z.object({
+  keyword: z.string().optional().openapi({ description: '搜索关键词', example: '书法' }),
+  city: z.string().optional().openapi({ description: '城市筛选', example: '北京' }),
+  minAge: z.coerce.number().int().min(50).optional().openapi({ description: '最小年龄' }),
+  maxAge: z.coerce.number().int().max(100).optional().openapi({ description: '最大年龄' }),
+  skills: z.array(z.string()).optional().openapi({ description: '技能筛选', example: ['书法', '国画'] }),
+  certified: z.coerce.boolean().optional().openapi({ description: '仅认证用户', example: true }),
+  available: z.coerce.boolean().optional().openapi({ description: '仅可服务用户', example: true }),
+  sortBy: z.enum(['createdAt', 'rating', 'popularity', 'experience']).default('createdAt').openapi({ description: '排序字段' }),
+  sortOrder: z.enum(['asc', 'desc']).default('desc').openapi({ description: '排序方向' }),
+  page: z.coerce.number().int().min(1).default(1).openapi({ description: '页码', example: 1 }),
+  pageSize: z.coerce.number().int().min(1).max(50).default(20).openapi({ description: '每页数量', example: 20 })
+});
+
+// 列表响应DTO
+export const SilverTalentListResponse = z.object({
+  data: z.array(SilverTalentSchema).openapi({ description: '人才列表数据' }),
+  pagination: z.object({
+    total: z.number().openapi({ example: 100, description: '总记录数' }),
+    current: z.number().openapi({ example: 1, description: '当前页码' }),
+    pageSize: z.number().openapi({ example: 20, description: '每页数量' }),
+    totalPages: z.number().openapi({ example: 5, description: '总页数' })
+  })
+});
+
+// 技能标签DTO
+export const TalentSkillDto = z.object({
+  skillName: z.string().min(1).max(50).openapi({ description: '技能名称', example: '书法' }),
+  skillLevel: z.enum(['初级', '中级', '高级', '专家']).default('中级').openapi({ description: '技能等级' }),
+  yearsExperience: z.coerce.number().int().min(0).default(0).openapi({ description: '经验年数', example: 10 }),
+  certificationUrl: z.string().url().optional().openapi({ description: '认证链接' })
+});
+
+// 响应类型定义
+export type SilverTalent = z.infer<typeof SilverTalentSchema>;
+export type CreateSilverTalentRequest = z.infer<typeof CreateSilverTalentDto>;
+export type UpdateSilverTalentRequest = z.infer<typeof UpdateSilverTalentDto>;
+export type SearchSilverTalentParams = z.infer<typeof SearchSilverTalentDto>;
+export type SilverTalentListResponseType = z.infer<typeof SilverTalentListResponse>;
+export type TalentSkill = z.infer<typeof TalentSkillDto>;

+ 283 - 0
src/server/modules/silver-users/silver-talent.service.ts

@@ -0,0 +1,283 @@
+import { DataSource, Repository } from 'typeorm';
+import { SilverUserProfile, Gender, CertificationStatus, JobSeekingStatus } from './silver-user-profile.entity';
+import { CreateSilverTalentRequest, UpdateSilverTalentRequest, SearchSilverTalentParams } from './silver-talent.dto';
+import debug from 'debug';
+
+const logger = debug('backend:service:silver-talent');
+
+export class SilverTalentService {
+  private repository: Repository<SilverUserProfile>;
+
+  constructor(dataSource: DataSource) {
+    this.repository = dataSource.getRepository(SilverUserProfile);
+  }
+
+  /**
+   * 获取银龄人才列表(支持分页、搜索、筛选)
+   */
+  async findAll(params: SearchSilverTalentParams): Promise<[SilverUserProfile[], number]> {
+    const {
+      keyword,
+      city,
+      minAge,
+      maxAge,
+      skills,
+      certified,
+      available,
+      sortBy = 'createdAt',
+      sortOrder = 'desc',
+      page = 1,
+      pageSize = 20
+    } = params;
+
+    const queryBuilder = this.repository.createQueryBuilder('profile');
+
+    // 关联用户基础信息
+    queryBuilder.leftJoinAndSelect('profile.user', 'user');
+
+    // 关键词搜索(姓名、简介、技能、机构)
+    if (keyword) {
+      queryBuilder.andWhere(
+        '(profile.realName LIKE :keyword OR profile.personalIntro LIKE :keyword OR profile.personalSkills LIKE :keyword OR profile.organization LIKE :keyword)',
+        { keyword: `%${keyword}%` }
+      );
+    }
+
+    // 年龄范围筛选
+    if (minAge !== undefined) {
+      queryBuilder.andWhere('profile.age >= :minAge', { minAge });
+    }
+    if (maxAge !== undefined) {
+      queryBuilder.andWhere('profile.age <= :maxAge', { maxAge });
+    }
+
+    // 技能筛选(模糊匹配)
+    if (skills && skills.length > 0) {
+      const skillConditions = skills.map((skill, index) => 
+        `profile.personalSkills LIKE :skill${index}`
+      ).join(' OR ');
+      
+      const skillParams = skills.reduce((acc, skill, index) => {
+        acc[`skill${index}`] = `%${skill}%`;
+        return acc;
+      }, {} as Record<string, string>);
+      
+      queryBuilder.andWhere(skillConditions, skillParams);
+    }
+
+    // 认证状态筛选
+    if (certified === true) {
+      queryBuilder.andWhere('profile.certificationStatus = :certifiedStatus', { 
+        certifiedStatus: CertificationStatus.CERTIFIED 
+      });
+    }
+
+    // 求职状态筛选(可服务用户)
+    if (available === true) {
+      queryBuilder.andWhere('profile.jobSeekingStatus IN (:...availableStatuses)', {
+        availableStatuses: [JobSeekingStatus.ACTIVELY_SEEKING, JobSeekingStatus.OPEN_TO_OPPORTUNITIES]
+      });
+    }
+
+    // 排序逻辑
+    switch (sortBy) {
+      case 'createdAt':
+        queryBuilder.orderBy('profile.createdAt', sortOrder.toUpperCase() as 'ASC' | 'DESC');
+        break;
+      case 'rating':
+        queryBuilder.orderBy('profile.knowledgeRankingScore', sortOrder.toUpperCase() as 'ASC' | 'DESC');
+        break;
+      case 'popularity':
+        queryBuilder.orderBy('profile.knowledgeReadCount', sortOrder.toUpperCase() as 'ASC' | 'DESC');
+        break;
+      case 'experience':
+        queryBuilder.orderBy('profile.timeBankHours', sortOrder.toUpperCase() as 'ASC' | 'DESC');
+        break;
+      default:
+        queryBuilder.orderBy('profile.createdAt', 'DESC');
+    }
+
+    // 分页
+    const skip = (page - 1) * pageSize;
+    queryBuilder.skip(skip).take(pageSize);
+
+    logger('Executing talent search query with params:', params);
+    return queryBuilder.getManyAndCount();
+  }
+
+  /**
+   * 根据ID获取人才详情
+   */
+  async findById(id: number): Promise<SilverUserProfile> {
+    const profile = await this.repository.findOne({
+      where: { id },
+      relations: ['user'],
+      select: [
+        'id', 'userId', 'realName', 'nickname', 'organization', 'age', 'gender',
+        'avatarUrl', 'personalIntro', 'personalSkills', 'personalExperience',
+        'certificationStatus', 'certificationInfo', 'jobSeekingStatus',
+        'jobSeekingRequirements', 'totalPoints', 'timeBankHours',
+        'knowledgeShareCount', 'knowledgeReadCount', 'knowledgeLikeCount',
+        'createdAt', 'updatedAt'
+      ]
+    });
+
+    if (!profile) {
+      throw new Error('未找到该银龄人才信息');
+    }
+
+    return profile;
+  }
+
+  /**
+   * 根据用户ID获取人才信息
+   */
+  async findByUserId(userId: number): Promise<SilverUserProfile | null> {
+    return this.repository.findOne({
+      where: { userId },
+      select: [
+        'id', 'userId', 'realName', 'nickname', 'organization', 'age', 'gender',
+        'phone', 'email', 'avatarUrl', 'personalIntro', 'personalSkills',
+        'personalExperience', 'certificationStatus', 'certificationInfo',
+        'jobSeekingStatus', 'jobSeekingRequirements', 'totalPoints',
+        'resumeCount', 'applicationCount', 'timeBankHours',
+        'knowledgeShareCount', 'knowledgeDownloadCount', 'knowledgeLikeCount',
+        'knowledgeReadCount', 'knowledgeFavoriteCount', 'knowledgeCommentCount',
+        'knowledgeRanking', 'knowledgeRankingScore', 'createdAt', 'updatedAt'
+      ]
+    });
+  }
+
+  /**
+   * 创建银龄人才档案
+   */
+  async create(userId: number, data: CreateSilverTalentRequest): Promise<SilverUserProfile> {
+    // 检查是否已存在档案
+    const existing = await this.repository.findOne({ where: { userId } });
+    if (existing) {
+      throw new Error('该用户已存在银龄人才档案');
+    }
+
+    const profile = this.repository.create({
+      ...data,
+      userId,
+      gender: data.gender as Gender,
+      createdBy: userId,
+      updatedBy: userId
+    });
+
+    return this.repository.save(profile);
+  }
+
+  /**
+   * 更新银龄人才档案
+   */
+  async update(id: number, userId: number, data: UpdateSilverTalentRequest): Promise<SilverUserProfile> {
+    const profile = await this.findById(id);
+    
+    if (profile.userId !== userId) {
+      throw new Error('只能修改自己的档案信息');
+    }
+
+    // 处理枚举类型转换
+    const updateData: any = { ...data };
+    if (data.gender) {
+      updateData.gender = data.gender as Gender;
+    }
+    if (data.jobSeekingStatus) {
+      updateData.jobSeekingStatus = data.jobSeekingStatus as JobSeekingStatus;
+    }
+
+    Object.assign(profile, {
+      ...updateData,
+      updatedBy: userId,
+      updatedAt: new Date()
+    });
+
+    return this.repository.save(profile);
+  }
+
+  /**
+   * 获取推荐人才(基于活跃度)
+   */
+  async getRecommendedTalents(limit: number = 10): Promise<SilverUserProfile[]> {
+    return this.repository.createQueryBuilder('profile')
+      .leftJoinAndSelect('profile.user', 'user')
+      .where('profile.certificationStatus = :certified', { certified: CertificationStatus.CERTIFIED })
+      .andWhere('profile.jobSeekingStatus != :notSeeking', { notSeeking: JobSeekingStatus.NOT_SEEKING })
+      .orderBy('profile.knowledgeRankingScore', 'DESC')
+      .addOrderBy('profile.timeBankHours', 'DESC')
+      .limit(limit)
+      .getMany();
+  }
+
+  /**
+   * 获取热门人才(基于浏览量)
+   */
+  async getPopularTalents(limit: number = 10): Promise<SilverUserProfile[]> {
+    return this.repository.createQueryBuilder('profile')
+      .leftJoinAndSelect('profile.user', 'user')
+      .orderBy('profile.knowledgeReadCount', 'DESC')
+      .limit(limit)
+      .getMany();
+  }
+
+  /**
+   * 更新认证状态
+   */
+  async updateCertificationStatus(id: number, userId: number, status: CertificationStatus, certificationInfo?: string): Promise<SilverUserProfile> {
+    const profile = await this.findById(id);
+    
+    if (profile.userId !== userId) {
+      throw new Error('权限不足');
+    }
+
+    profile.certificationStatus = status;
+    if (certificationInfo !== undefined) {
+      profile.certificationInfo = certificationInfo;
+    }
+    profile.updatedBy = userId;
+    profile.updatedAt = new Date();
+
+    return this.repository.save(profile);
+  }
+
+  /**
+   * 获取用户自己的档案
+   */
+  async getMyProfile(userId: number): Promise<SilverUserProfile | null> {
+    return this.repository.findOne({ where: { userId } });
+  }
+
+  /**
+   * 检查用户是否有人才档案
+   */
+  async hasProfile(userId: number): Promise<boolean> {
+    const count = await this.repository.count({ where: { userId } });
+    return count > 0;
+  }
+
+  /**
+   * 统计人才总数
+   */
+  async getTotalCount(): Promise<number> {
+    return this.repository.count();
+  }
+
+  /**
+   * 按城市统计人才分布
+   */
+  async getCityStats(): Promise<Array<{ city: string; count: number }>> {
+    const result = await this.repository.createQueryBuilder('profile')
+      .select('SUBSTRING_INDEX(profile.organization, " ", 1)', 'city')
+      .addSelect('COUNT(*)', 'count')
+      .groupBy('city')
+      .orderBy('count', 'DESC')
+      .getRawMany();
+    
+    return result.map(item => ({
+      city: item.city || '未知',
+      count: parseInt(item.count)
+    }));
+  }
+}