Browse Source

✨ feat(mobile): 添加首页动态图标管理功能

- 创建useHomeIcons hook实现轮播图和分类图标的数据获取
- 实现动态图标查询键管理和缓存策略
- 添加5分钟数据缓存和10分钟垃圾回收时间

♻️ refactor(mobile): 优化首页数据加载逻辑

- 迁移轮播图数据获取到useHomeIcons hook
- 删除已过时的transformPolicyNews方法
- 添加图标数据加载状态管理

🐛 fix(mobile): 修复岗位详情页ID类型错误

- 将岗位ID参数从字符串类型改为数字类型
- 解决岗位详情页数据获取失败问题

🔧 chore(server): 调整首页数据接口结构

- 移除homeData中的banners字段
- 删除HomeService中的getBanners方法
- 添加轮播图功能迁移注释说明
yourname 7 months ago
parent
commit
c30a3e338a

+ 89 - 0
src/client/mobile/hooks/useHomeIcons.ts

@@ -0,0 +1,89 @@
+import { useQuery } from '@tanstack/react-query';
+import { homeIconClient } from '@/client/api';
+import { useAuth } from './AuthProvider';
+
+// 查询键
+export const homeIconKeys = {
+  all: ['home-icons'] as const,
+  banners: () => [...homeIconKeys.all, 'banners'] as const,
+  categories: () => [...homeIconKeys.all, 'categories'] as const,
+};
+
+/**
+ * 获取首页轮播图
+ */
+export const useHomeBanners = () => {
+  const { user } = useAuth();
+  
+  return useQuery({
+    queryKey: homeIconKeys.banners(),
+    queryFn: async () => {
+      const response = await homeIconClient.$get({
+        query: {
+          keyword: 'banner',
+          filters: JSON.stringify({ type: 'banner', isEnabled: 1 }),
+          page: 1,
+          pageSize: 10
+        }
+      });
+      if (response.status !== 200) {
+        throw new Error('获取轮播图失败');
+      }
+      const data = await response.json();
+      return data.data || [];
+    },
+    staleTime: 5 * 60 * 1000, // 5分钟
+    gcTime: 10 * 60 * 1000,
+    enabled: !!user,
+  });
+};
+
+/**
+ * 获取首页分类图标
+ */
+export const useHomeCategories = () => {
+  const { user } = useAuth();
+  
+  return useQuery({
+    queryKey: homeIconKeys.categories(),
+    queryFn: async () => {
+      const response = await homeIconClient.$get({
+        query: {
+          keyword: 'category',
+          filters: JSON.stringify({ type: 'category', isEnabled: 1 }),
+          page: 1,
+          pageSize: 20
+        }
+      });
+      if (response.status !== 200) {
+        throw new Error('获取分类图标失败');
+      }
+      const data = await response.json();
+      return data.data || [];
+    },
+    staleTime: 5 * 60 * 1000,
+    gcTime: 10 * 60 * 1000,
+    enabled: !!user,
+  });
+};
+
+/**
+ * 获取完整的图标数据
+ */
+export const useHomeIcons = () => {
+  const { user } = useAuth();
+  
+  const banners = useHomeBanners();
+  const categories = useHomeCategories();
+  
+  return {
+    banners: banners.data || [],
+    categories: categories.data || [],
+    isLoading: banners.isLoading || categories.isLoading,
+    isError: banners.isError || categories.isError,
+    refetch: () => {
+      banners.refetch();
+      categories.refetch();
+    }
+  };
+};

+ 1 - 1
src/client/mobile/pages/JobDetailPage.tsx

@@ -52,7 +52,7 @@ const JobDetailPage: React.FC = () => {
     queryKey: ['job-detail', id],
     queryFn: async () => {
       const response = await silverJobClient[':id']['$get']({
-        param: { id: String(id) }
+        param: { id: Number(id) }
       });
       if (!response.ok) {
         throw new Error('获取岗位信息失败');

+ 74 - 13
src/client/mobile/pages/NewHomePage.tsx

@@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef } from 'react';
 import { useNavigate } from 'react-router-dom';
 import { useHomeData } from '../hooks/useHomeData';
 import { useAuth } from '../hooks/AuthProvider';
+import { useHomeIcons } from '../hooks/useHomeIcons';
 import { EnhancedCarousel } from '../components/EnhancedCarousel';
 import { AdBannerCarousel } from '../components/AdBannerCarousel';
 import { SkeletonLoader, BannerSkeleton, ListItemSkeleton } from '../components/SkeletonLoader';
@@ -112,16 +113,8 @@ const serviceCategories = [
   }
 ];
 
-// 数据转换工具
-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] || unsplash.getPlaceholderImage('community', item.id),
-    fallbackImage: '/images/placeholder-banner.jpg',
-    link: `/policy-news/${item.id}`
-  }));
+// 政策资讯转换工具(已迁移到HomeIcons模块)
+// 轮播图数据现在通过 useHomeIcons 获取
 
 const transformJobs = (jobs: any[]) =>
   jobs.map(job => ({
@@ -237,7 +230,10 @@ const NewHomePage: React.FC = () => {
   const handlePullToRefresh = async () => {
     setIsPulling(true);
     try {
-      await refetch();
+      await Promise.all([
+        refetch(),
+        // 这里可以添加图标数据的刷新
+      ]);
     } finally {
       setIsPulling(false);
     }
@@ -256,8 +252,16 @@ const NewHomePage: React.FC = () => {
     }
   };
 
+  // 获取动态图标数据
+  const {
+    banners: dynamicBanners,
+    categories: dynamicCategories,
+    isLoading: isIconsLoading,
+    isError: isIconsError
+  } = useHomeIcons();
+
   // 加载状态
-  if (isLoading || !homeData) {
+  if (isLoading || !homeData || isIconsLoading) {
     return (
       <div className="min-h-screen" style={{ backgroundColor: COLORS.ink.light }}>
         <HeaderSkeleton />
@@ -270,7 +274,6 @@ const NewHomePage: React.FC = () => {
   }
 
   // 数据转换与排序
-  const banners = transformPolicyNews(homeData.banners || []);
   const recommendedJobs = transformJobs(homeData.recommendedJobs || [])
     .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
     .slice(0, 3);
@@ -290,6 +293,64 @@ const NewHomePage: React.FC = () => {
 
   const timeBankActivities = transformTimeBank(homeData.timeBankActivities || []);
 
+  // 转换轮播图数据(来自HomeIcons管理)
+  const banners = dynamicBanners.map(banner => ({
+    id: banner.id,
+    title: banner.title,
+    description: banner.description || '',
+    image: banner.file?.path || unsplash.getPlaceholderImage('banner', banner.id),
+    fallbackImage: '/images/placeholder-banner.jpg',
+    link: banner.linkUrl || '#'
+  }));
+
+  // 转换分类图标数据
+  const categories = dynamicCategories.length > 0
+    ? dynamicCategories.map(category => ({
+        name: category.title,
+        icon: BriefcaseIcon, // 默认图标,后续可根据需要扩展
+        path: category.linkUrl || '/',
+        color: COLORS.accent.blue,
+        image: category.file?.path
+      }))
+    : [
+        {
+          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
+        }
+      ];
+
   return (
     <div className="min-h-screen" style={{ backgroundColor: COLORS.ink.light }}>
       {/* 顶部导航栏 */}

+ 0 - 2
src/server/api/home/index.ts

@@ -8,7 +8,6 @@ import { ErrorSchema } from '@/server/utils/errorHandler';
 
 // 响应Schema
 const HomeResponse = z.object({
-  banners: z.array(z.any()),
   recommendedJobs: z.array(z.any()),
   hotKnowledge: z.array(z.any()),
   timeBankActivities: z.array(z.any()),
@@ -109,7 +108,6 @@ const app = new OpenAPIHono<AuthContext>()
     const homeData = await homeService.getHomeData(userId);
 
     // 限制返回数量
-    homeData.banners = homeData.banners.slice(0, Math.min(limit, 5));
     homeData.recommendedJobs = homeData.recommendedJobs.slice(0, Math.min(limit, 6));
     homeData.hotKnowledge = homeData.hotKnowledge.slice(0, Math.min(limit, 4));
     homeData.timeBankActivities = homeData.timeBankActivities.slice(0, Math.min(limit, 3));

+ 2 - 14
src/server/modules/home/home.service.ts

@@ -11,7 +11,6 @@ import { SilverKnowledgeInteraction } from '../silver-users/silver-knowledge-int
 import { UserPreference } from '../silver-users/user-preference.entity';
 
 export interface HomeData {
-  banners: PolicyNews[];
   recommendedJobs: Job[];
   hotKnowledge: SilverKnowledge[];
   timeBankActivities: SilverTimeBank[];
@@ -65,7 +64,6 @@ export class HomeService {
    */
   async getHomeData(userId?: number): Promise<HomeData> {
     const [
-      banners,
       recommendedJobs,
       hotKnowledge,
       timeBankActivities,
@@ -73,7 +71,6 @@ export class HomeService {
       silverPositions,
       userStats
     ] = await Promise.all([
-      this.getBanners(),
       this.getRecommendedJobs(userId),
       this.getHotKnowledge(userId),
       this.getTimeBankActivities(userId),
@@ -83,7 +80,6 @@ export class HomeService {
     ]);
 
     return {
-      banners,
       recommendedJobs,
       hotKnowledge,
       timeBankActivities,
@@ -93,16 +89,8 @@ export class HomeService {
     };
   }
 
-  /**
-   * 获取轮播图(精选政策新闻)
-   */
-  async getBanners(limit: number = 5): Promise<PolicyNews[]> {
-    return await this.policyNewsRepo.find({
-      where: { isFeatured: 1 },
-      order: { createdAt: 'DESC' },
-      take: limit
-    });
-  }
+  // 轮播图功能已迁移到首页轮播图标管理模块
+  // 相关方法请使用 HomeIconService.getHomeBanners()
 
   /**
    * 获取推荐岗位(基于用户画像和地理位置)