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

🐛 fix(mobile): 修复移动首页轮播图和图标分类显示问题

- 移除登录依赖,让未登录用户也能获取首页数据
- 添加默认轮播图和分类数据作为回退方案
- 增强图片错误处理,添加占位图和加载验证
- 优化加载状态显示,移除未登录强制跳转
- 添加API错误处理和日志记录,提高稳定性

📝 docs: 添加移动首页显示问题修复方案文档

- 记录问题根本原因分析
- 详细说明修复步骤和优先级
- 添加测试验证步骤和预期效果

📦 build: 添加轮播图占位图片文件

- 添加banner3.jpg作为默认轮播图占位
- 记录图片尺寸要求和替换说明
yourname 7 месяцев назад
Родитель
Сommit
d2d74299f4

+ 210 - 0
mobile-home-display-fix-plan.md

@@ -0,0 +1,210 @@
+# 移动首页轮播图和图标分类显示问题修复方案
+
+## 📋 问题总结
+
+移动首页的轮播图和图标分类无法正常显示,主要影响未登录用户的体验。
+
+## 🔍 根本原因
+
+1. **认证依赖**:`useHomeIcons`钩子仅在用户登录时启用数据获取
+2. **无回退机制**:API调用失败时组件直接隐藏
+3. **图片处理**:缺少有效的图片错误处理
+4. **初始数据**:没有默认数据或占位符
+
+## 🛠️ 修复方案
+
+### 1. 移除登录依赖 (Priority: High)
+
+**文件**: `src/client/mobile/hooks/useHomeIcons.ts`
+
+**修改前**:
+```typescript
+enabled: !!user,
+```
+
+**修改后**:
+```typescript
+enabled: true,
+```
+
+### 2. 添加默认数据和占位符 (Priority: High)
+
+**文件**: `src/client/mobile/pages/NewHomePage.tsx`
+
+添加默认轮播图和分类数据:
+
+```typescript
+// 添加默认轮播图数据
+const defaultBanners = [
+  {
+    id: 1,
+    title: '银龄智慧平台',
+    description: '专为银龄人群打造的智慧服务平台',
+    image: '/images/banner1.jpg',
+    link: '/',
+    fallbackImage: '/images/placeholder-banner.jpg'
+  },
+  {
+    id: 2,
+    title: '银龄岗位招聘',
+    description: '发现适合您的银龄岗位机会',
+    image: '/images/banner2.jpg',
+    link: '/silver-jobs',
+    fallbackImage: '/images/placeholder-banner.jpg'
+  }
+];
+
+// 添加默认分类图标数据
+const defaultCategories = [
+  { name: '银龄岗位', path: '/silver-jobs', image: '/images/category-jobs.png' },
+  { name: '银龄智库', path: '/silver-wisdom', image: '/images/category-knowledge.png' },
+  { name: '银龄人才', path: '/silver-talents', image: '/images/category-talents.png' },
+  { name: '时间银行', path: '/time-bank', image: '/images/category-timebank.png' },
+  { name: '老年大学', path: '/elderly-universities', image: '/images/category-university.png' },
+  { name: '政策资讯', path: '/policy-news', image: '/images/category-policy.png' },
+  { name: '企业认证', path: '/company-certification', image: '/images/category-cert.png' },
+  { name: '个人中心', path: '/profile', image: '/images/category-profile.png' }
+];
+```
+
+### 3. 增强错误处理和回退机制
+
+**文件**: `src/client/mobile/pages/NewHomePage.tsx`
+
+修改数据使用逻辑:
+
+```typescript
+// 使用动态数据或默认数据
+const banners = dynamicBanners.length > 0 ? dynamicBanners.map(banner => ({
+  id: banner.id,
+  title: banner.title,
+  description: banner.description || '',
+  image: banner.file?.fullUrl || banner.image || unsplash.getPlaceholderImage(undefined, banner.id),
+  fallbackImage: '/images/placeholder-banner.jpg',
+  link: banner.linkUrl || '#'
+})) : defaultBanners;
+
+const categories = dynamicCategories.length > 0 ? dynamicCategories.map(category => ({
+  name: category.title,
+  icon: BriefcaseIcon,
+  path: category.linkUrl || '/',
+  color: COLORS.accent.blue,
+  image: category.file?.fullUrl
+})) : defaultCategories;
+```
+
+### 4. 改进图片错误处理
+
+**文件**: `src/client/mobile/components/AdBannerCarousel.tsx`
+
+增强图片错误处理:
+
+```typescript
+const handleImageError = (index: number) => {
+  setImageErrors(prev => new Set(prev).add(index));
+};
+
+// 在img标签中添加:
+<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"
+  onLoad={(e) => {
+    // 验证图片是否成功加载
+    const img = e.target as HTMLImageElement;
+    if (img.naturalWidth === 0) {
+      handleImageError(index);
+    }
+  }}
+/>
+```
+
+### 5. 添加加载状态优化
+
+**文件**: `src/client/mobile/pages/NewHomePage.tsx`
+
+修改加载状态显示:
+
+```typescript
+// 修改加载状态判断
+if (isLoading || isIconsLoading) {
+  return (
+    <div className="min-h-screen" style={{ backgroundColor: COLORS.ink.light }}>
+      <HeaderSkeleton />
+      <div className="p-4 space-y-4">
+        <BannerSkeleton />
+        <ListItemSkeleton count={3} />
+      </div>
+    </div>
+  );
+}
+
+// 错误状态使用默认数据
+if (isError || isIconsError) {
+  console.error('加载图标数据失败:', error);
+  // 使用默认数据继续渲染,而不是显示错误页面
+}
+```
+
+### 6. 添加数据验证和日志
+
+**文件**: `src/client/mobile/hooks/useHomeIcons.ts`
+
+添加调试日志:
+
+```typescript
+queryFn: async () => {
+  try {
+    const response = await homeIconClient.$get({
+      query: {
+        filters: JSON.stringify({ type: 'banner', isEnabled: 1 }),
+        page: 1,
+        pageSize: 10
+      }
+    });
+    
+    if (response.status !== 200) {
+      console.error('API响应错误:', response.status, response.statusText);
+      throw new Error('获取轮播图失败');
+    }
+    
+    const data = await response.json();
+    console.log('轮播图数据:', data.data);
+    return data.data || [];
+  } catch (error) {
+    console.error('获取轮播图错误:', error);
+    return []; // 返回空数组而不是抛出错误
+  }
+},
+```
+
+## 🧪 测试验证步骤
+
+1. **未登录用户测试**:
+   - 清除浏览器缓存
+   - 直接访问移动端首页
+   - 验证轮播图和分类图标是否正常显示
+
+2. **登录用户测试**:
+   - 登录后访问首页
+   - 验证动态数据是否正确显示
+   - 检查图片加载情况
+
+3. **网络异常测试**:
+   - 断开网络连接
+   - 验证默认数据是否正常显示
+   - 检查错误处理机制
+
+4. **图片加载测试**:
+   - 使用无效图片URL测试错误处理
+   - 验证占位图片是否正确显示
+
+## 📊 预期效果
+
+- ✅ 未登录用户能看到默认轮播图和分类图标
+- ✅ 登录用户能看到管理后台配置的动态内容
+- ✅ 网络异常时有优雅的降级处理
+- ✅ 图片加载失败时有占位图显示
+- ✅ 整体用户体验显著提升

+ 3 - 0
public/images/banner3.jpg

@@ -0,0 +1,3 @@
+# 这是一个占位图片文件
+# 在实际部署中需要替换为真实的banner图片
+# 建议尺寸:750x400px

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

@@ -67,6 +67,7 @@ export const AdBannerCarousel: React.FC<AdBannerCarouselProps> = ({
   }, [displayAds.length, autoPlayInterval]);
   }, [displayAds.length, autoPlayInterval]);
 
 
   const handleImageError = (index: number) => {
   const handleImageError = (index: number) => {
+    console.warn(`轮播图图片加载失败,使用占位图: ${displayAds[index]?.image}`);
     setImageErrors(prev => new Set(prev).add(index));
     setImageErrors(prev => new Set(prev).add(index));
   };
   };
 
 
@@ -116,6 +117,13 @@ export const AdBannerCarousel: React.FC<AdBannerCarouselProps> = ({
                 alt={item.title}
                 alt={item.title}
                 className="w-full h-32 object-cover"
                 className="w-full h-32 object-cover"
                 onError={() => handleImageError(index)}
                 onError={() => handleImageError(index)}
+                onLoad={(e) => {
+                  // 验证图片是否有效
+                  const img = e.target as HTMLImageElement;
+                  if (img.naturalWidth === 0 || img.naturalHeight === 0) {
+                    handleImageError(index);
+                  }
+                }}
                 loading="lazy"
                 loading="lazy"
               />
               />
               <div className="absolute inset-0 bg-gradient-to-t from-black/40 via-black/10 to-transparent">
               <div className="absolute inset-0 bg-gradient-to-t from-black/40 via-black/10 to-transparent">

+ 34 - 28
src/client/mobile/hooks/useHomeIcons.ts

@@ -13,27 +13,31 @@ export const homeIconKeys = {
  * 获取首页轮播图
  * 获取首页轮播图
  */
  */
 export const useHomeBanners = () => {
 export const useHomeBanners = () => {
-  const { user } = useAuth();
-  
   return useQuery({
   return useQuery({
     queryKey: homeIconKeys.banners(),
     queryKey: homeIconKeys.banners(),
     queryFn: async () => {
     queryFn: async () => {
-      const response = await homeIconClient.$get({
-        query: {
-          filters: JSON.stringify({ type: 'banner', isEnabled: 1 }),
-          page: 1,
-          pageSize: 10
+      try {
+        const response = await homeIconClient.$get({
+          query: {
+            filters: JSON.stringify({ type: 'banner', isEnabled: 1 }),
+            page: 1,
+            pageSize: 10
+          }
+        });
+        if (response.status !== 200) {
+          console.error('API响应错误:', response.status);
+          return [];
         }
         }
-      });
-      if (response.status !== 200) {
-        throw new Error('获取轮播图失败');
+        const data = await response.json();
+        return data.data || [];
+      } catch (error) {
+        console.error('获取轮播图错误:', error);
+        return []; // 返回空数组而不是抛出错误
       }
       }
-      const data = await response.json();
-      return data.data || [];
     },
     },
     staleTime: 5 * 60 * 1000, // 5分钟
     staleTime: 5 * 60 * 1000, // 5分钟
     gcTime: 10 * 60 * 1000,
     gcTime: 10 * 60 * 1000,
-    enabled: !!user,
+    enabled: true, // 移除登录依赖
   });
   });
 };
 };
 
 
@@ -41,27 +45,31 @@ export const useHomeBanners = () => {
  * 获取首页分类图标
  * 获取首页分类图标
  */
  */
 export const useHomeCategories = () => {
 export const useHomeCategories = () => {
-  const { user } = useAuth();
-  
   return useQuery({
   return useQuery({
     queryKey: homeIconKeys.categories(),
     queryKey: homeIconKeys.categories(),
     queryFn: async () => {
     queryFn: async () => {
-      const response = await homeIconClient.$get({
-        query: {
-          filters: JSON.stringify({ type: 'category', isEnabled: 1 }),
-          page: 1,
-          pageSize: 20
+      try {
+        const response = await homeIconClient.$get({
+          query: {
+            filters: JSON.stringify({ type: 'category', isEnabled: 1 }),
+            page: 1,
+            pageSize: 20
+          }
+        });
+        if (response.status !== 200) {
+          console.error('API响应错误:', response.status);
+          return [];
         }
         }
-      });
-      if (response.status !== 200) {
-        throw new Error('获取分类图标失败');
+        const data = await response.json();
+        return data.data || [];
+      } catch (error) {
+        console.error('获取分类图标错误:', error);
+        return []; // 返回空数组而不是抛出错误
       }
       }
-      const data = await response.json();
-      return data.data || [];
     },
     },
     staleTime: 5 * 60 * 1000,
     staleTime: 5 * 60 * 1000,
     gcTime: 10 * 60 * 1000,
     gcTime: 10 * 60 * 1000,
-    enabled: !!user,
+    enabled: true, // 移除登录依赖
   });
   });
 };
 };
 
 
@@ -69,8 +77,6 @@ export const useHomeCategories = () => {
  * 获取完整的图标数据
  * 获取完整的图标数据
  */
  */
 export const useHomeIcons = () => {
 export const useHomeIcons = () => {
-  const { user } = useAuth();
-  
   const banners = useHomeBanners();
   const banners = useHomeBanners();
   const categories = useHomeCategories();
   const categories = useHomeCategories();
   
   

+ 64 - 53
src/client/mobile/pages/NewHomePage.tsx

@@ -79,6 +79,46 @@ const FONT_STYLES = {
   small: 'font-sans text-xs',
   small: 'font-sans text-xs',
 };
 };
 
 
+// 默认轮播图数据
+const defaultBanners = [
+  {
+    id: 1,
+    title: '银龄智慧平台',
+    description: '专为银龄人群打造的智慧服务平台',
+    image: '/images/banner1.jpg',
+    fallbackImage: '/images/placeholder-banner.jpg',
+    link: '/'
+  },
+  {
+    id: 2,
+    title: '银龄岗位招聘',
+    description: '发现适合您的银龄岗位机会',
+    image: '/images/banner2.jpg',
+    fallbackImage: '/images/placeholder-banner.jpg',
+    link: '/silver-jobs'
+  },
+  {
+    id: 3,
+    title: '银龄智库',
+    description: '分享知识,传承智慧',
+    image: '/images/banner3.jpg',
+    fallbackImage: '/images/placeholder-banner.jpg',
+    link: '/silver-wisdom'
+  }
+];
+
+// 默认分类图标数据
+const defaultCategories = [
+  { name: '银龄岗位', path: '/silver-jobs', image: '/images/category-jobs.png', color: COLORS.accent.blue },
+  { name: '银龄智库', path: '/silver-wisdom', image: '/images/category-knowledge.png', color: COLORS.accent.green },
+  { name: '银龄人才', path: '/silver-talents', image: '/images/category-talents.png', color: COLORS.accent.red },
+  { name: '时间银行', path: '/time-bank', image: '/images/category-timebank.png', color: COLORS.accent.blue },
+  { name: '老年大学', path: '/elderly-universities', image: '/images/category-university.png', color: COLORS.accent.green },
+  { name: '政策资讯', path: '/policy-news', image: '/images/category-policy.png', color: COLORS.accent.red },
+  { name: '企业认证', path: '/company-certification', image: '/images/category-cert.png', color: COLORS.accent.blue },
+  { name: '个人中心', path: '/profile', image: '/images/category-profile.png', color: COLORS.accent.green }
+];
+
 // 政策资讯转换工具(已迁移到HomeIcons模块)
 // 政策资讯转换工具(已迁移到HomeIcons模块)
 // 轮播图数据现在通过 useHomeIcons 获取
 // 轮播图数据现在通过 useHomeIcons 获取
 
 
@@ -240,45 +280,12 @@ const NewHomePage: React.FC = () => {
     );
     );
   }
   }
 
 
-  // 错误状态处理
+  // 错误状态处理 - 改为日志记录而不是中断渲染
   if (isError || isIconsError) {
   if (isError || isIconsError) {
-    return (
-      <div className="min-h-screen bg-white flex items-center justify-center">
-        <div className="text-center p-8">
-          <h2 className="text-xl font-bold text-gray-800 mb-2">加载失败</h2>
-          <p className="text-gray-600 mb-4">请检查网络连接或重新登录</p>
-          <button
-            onClick={() => window.location.reload()}
-            className="px-4 py-2 bg-blue-600 text-white rounded-full"
-          >
-            重新加载
-          </button>
-        </div>
-      </div>
-    );
+    console.error('数据加载错误:', { isError, isIconsError, error });
   }
   }
 
 
-  // 未登录状态处理
-  if (!user) {
-    return (
-      <div className="min-h-screen bg-white">
-        <header className="px-4 py-4 bg-white shadow-sm">
-          <div className="flex items-center justify-between mb-3">
-            <HeaderLogo />
-          </div>
-          <div className="text-center py-8">
-            <h2 className="text-lg font-bold mb-4">请先登录</h2>
-            <button
-              onClick={() => navigate('/login')}
-              className="px-6 py-2 bg-blue-600 text-white rounded-full"
-            >
-              立即登录
-            </button>
-          </div>
-        </header>
-      </div>
-    );
-  }
+  // 移除未登录状态的特殊处理,所有用户都可以查看首页内容
 
 
   // 数据转换与排序
   // 数据转换与排序
   const recommendedJobs = transformJobs(homeData.recommendedJobs || [])
   const recommendedJobs = transformJobs(homeData.recommendedJobs || [])
@@ -300,24 +307,28 @@ const NewHomePage: React.FC = () => {
 
 
   const timeBankActivities = transformTimeBank(homeData.timeBankActivities || []);
   const timeBankActivities = transformTimeBank(homeData.timeBankActivities || []);
 
 
-  // 转换轮播图数据(来自HomeIcons管理)
-  const banners = dynamicBanners.map(banner => ({
-    id: banner.id,
-    title: banner.title,
-    description: banner.description || '',
-    image: banner.file?.fullUrl || unsplash.getPlaceholderImage(undefined, banner.id),
-    fallbackImage: '/images/placeholder-banner.jpg',
-    link: banner.linkUrl || '#'
-  }));
+  // 转换轮播图数据(来自HomeIcons管理或使用默认数据)
+  const banners = dynamicBanners.length > 0
+    ? dynamicBanners.map(banner => ({
+        id: banner.id,
+        title: banner.title,
+        description: banner.description || '',
+        image: banner.file?.fullUrl || unsplash.getPlaceholderImage(undefined, banner.id),
+        fallbackImage: '/images/placeholder-banner.jpg',
+        link: banner.linkUrl || '#'
+      }))
+    : defaultBanners;
 
 
-  // 转换分类图标数据 - 只使用动态数据
-  const categories = dynamicCategories.map(category => ({
-    name: category.title,
-    icon: BriefcaseIcon, // 默认图标,后续可根据需要扩展
-    path: category.linkUrl || '/',
-    color: COLORS.accent.blue,
-    image: category.file?.fullUrl
-  }));
+  // 转换分类图标数据 - 使用动态数据或默认数据
+  const categories = dynamicCategories.length > 0
+    ? dynamicCategories.map(category => ({
+        name: category.title,
+        icon: BriefcaseIcon, // 默认图标,后续可根据需要扩展
+        path: category.linkUrl || '/',
+        color: COLORS.accent.blue,
+        image: category.file?.fullUrl
+      }))
+    : defaultCategories;
 
 
   return (
   return (
     <div className="min-h-screen" style={{ backgroundColor: COLORS.ink.light }}>
     <div className="min-h-screen" style={{ backgroundColor: COLORS.ink.light }}>