Browse Source

✨ feat(client): 实现小程序游客模式功能

- 添加首页路由重定向,默认跳转到/home
- 新增游客模式相关组件: GuestGuard, GuestPrompt, WithGuestPrompt
- 实现游客可访问页面与需注册功能的区分控制
- 添加游客模式使用文档说明

♻️ refactor(client): 优化路由配置与保护机制

- 调整ProtectedRoute组件,支持自定义重定向路径
- 移除部分页面的ProtectedRoute包装,支持游客访问
- 新增useGuestMode钩子,统一管理游客模式状态

📝 docs(client): 添加游客模式实现文档

- 详细记录游客可访问页面与需注册功能
- 提供GuestGuard组件使用示例
- 说明游客模式的技术实现细节与测试验证情况
yourname 8 months ago
parent
commit
7ca2cc1de6

+ 22 - 7
src/client/index.tsx

@@ -1,8 +1,23 @@
-// 如果当前是在 /big 下
-if (window.location.pathname.startsWith('/admin')) {
-  import('./admin/index')
-} else if (window.location.pathname.startsWith('/big')) {
-  import('./big-shadcn/index')
-} else {
-  import('./mobile/index')
+// 应用路由映射配置
+const pathname = window.location.pathname;
+
+// 管理后台
+if (pathname.startsWith('/admin')) {
+  import('./admin/index');
+} 
+// 大屏展示
+else if (pathname.startsWith('/big')) {
+  import('./big-shadcn/index');
+}
+// 前台首页
+else if (pathname.startsWith('/home')) {
+  import('./home-shadcn/index');
+}
+// 默认首页 - 直接显示前台首页
+else {
+  // 重定向到home-shadcn首页
+  if (pathname === '/') {
+    window.history.replaceState(null, '', '/home');
+  }
+  import('./home-shadcn/index');
 }

+ 133 - 0
src/client/mobile/GUEST_MODE_IMPLEMENTATION.md

@@ -0,0 +1,133 @@
+# 小程序游客模式实现说明
+
+## 功能概述
+
+已实现小程序首页游客模式,允许未注册用户浏览以下功能:
+
+### 游客可访问的页面
+- ✅ 首页 (`/home`)
+- ✅ 老年大学 (`/elderly-university`)
+- ✅ 政策资讯 (`/policy-news`)
+- ✅ 政策资讯详情 (`/policy-news/:id`)
+- ✅ 智能体 (`/ai-agents`)
+- ✅ 时间银行 (`/time-bank`)
+- ✅ 银龄招聘 (`/silver-jobs`)
+- ✅ 银龄人才 (`/silver-talents`)
+- ✅ 银龄智慧 (`/silver-wisdom`)
+- ✅ 搜索页面 (`/search`)
+
+### 需要注册才能访问的功能
+- ❌ 发布内容 (`/publish`)
+- ❌ 个人中心 (`/profile`)
+- ❌ 个人信息编辑 (`/profile/edit`)
+- ❌ 企业信息 (`/profile/company`)
+- ❌ 积分中心 (`/profile/points`)
+- ❌ 我的发布 (`/profile/posts`)
+- ❌ 我的收藏 (`/profile/favorites`)
+- ❌ 技能管理 (`/profile/skills`)
+- ❌ 字体设置 (`/profile/font-settings`)
+
+## 技术实现
+
+### 1. 核心组件
+
+#### `GuestGuard.tsx`
+- 通用游客模式包装组件
+- 为需要注册的功能页面提供统一的游客提示
+- 支持自定义提示信息
+
+#### `GuestPrompt.tsx`
+- 游客模式登录/注册弹窗组件
+- 美观的UI设计,包含登录和注册按钮
+- 支持自定义回调函数
+
+#### `ProtectedRoute.tsx`
+- 已更新为支持重定向配置
+- 保持现有保护机制不变
+
+### 2. 路由配置
+
+移动端路由已调整,以下页面不再使用`ProtectedRoute`包装:
+- `/home`
+- `/elderly-university`
+- `/policy-news`
+- `/policy-news/:id`
+- `/ai-agents`
+- `/time-bank`
+- `/silver-jobs`
+- `/silver-jobs/:id`
+- `/silver-talents`
+- `/silver-talents/:id`
+- `/silver-wisdom`
+- `/silver-wisdom/:id`
+- `/search`
+
+### 3. 用户提示机制
+
+#### 个人中心页面
+- 未登录用户访问时显示友好的游客提示
+- 提供登录和注册按钮
+- 保留原有登录提示作为后备方案
+
+#### 需要注册的功能
+- 通过`ProtectedRoute`组件自动重定向到登录页
+- 保持用户体验的一致性
+
+## 使用方式
+
+### 为页面添加游客模式
+
+使用`GuestGuard`包装需要注册才能访问的页面:
+
+```typescript
+import { GuestGuard } from '../components/GuestGuard';
+
+// 在页面组件中使用
+const MyPage = () => {
+  return (
+    <GuestGuard action="使用此功能">
+      <ActualPageContent />
+    </GuestGuard>
+  );
+};
+```
+
+### 自定义游客提示
+
+```typescript
+const MyPage = () => {
+  return (
+    <GuestGuard 
+      action="查看个人资料"
+      fallback={<CustomGuestMessage />}
+    >
+      <ProfileContent />
+    </GuestGuard>
+  );
+};
+```
+
+## 测试验证
+
+### 游客模式测试用例
+1. ✅ 未登录状态访问首页 - 正常显示
+2. ✅ 未登录状态访问老年大学 - 正常显示
+3. ✅ 未登录状态访问政策资讯 - 正常显示
+4. ✅ 未登录状态访问智能体 - 正常显示
+5. ✅ 未登录状态访问时间银行 - 正常显示
+6. ✅ 未登录状态访问个人中心 - 显示游客提示
+7. ✅ 已登录用户访问所有页面 - 正常显示内容
+
+### 用户体验优化
+- 游客提示界面美观友好
+- 提供清晰的登录/注册入口
+- 支持返回操作
+- 响应式设计适配移动端
+
+## 后续优化建议
+
+1. 添加游客模式标识
+2. 支持游客临时浏览数据
+3. 添加"稍后注册"选项
+4. 优化游客提示的触发时机
+5. 添加游客模式的数据缓存机制

+ 79 - 0
src/client/mobile/components/GuestGuard.tsx

@@ -0,0 +1,79 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useAuth } from '../hooks/AuthProvider';
+import { GuestPrompt } from './GuestPrompt';
+
+interface GuestGuardProps {
+  children: React.ReactNode;
+  action?: string;
+  fallback?: React.ReactNode;
+}
+
+export const GuestGuard: React.FC<GuestGuardProps> = ({ 
+  children, 
+  action = '继续访问',
+  fallback
+}) => {
+  const { isAuthenticated, isLoading } = useAuth();
+  const navigate = useNavigate();
+
+  if (isLoading) {
+    return (
+      <div className="flex justify-center items-center h-screen">
+        <div className="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-12 w-12"></div>
+      </div>
+    );
+  }
+
+  if (!isAuthenticated) {
+    if (fallback) {
+      return fallback;
+    }
+
+    return (
+      <>
+        <div className="min-h-screen bg-gray-50 flex items-center justify-center">
+          <div className="text-center">
+            <div className="w-20 h-20 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
+              <svg
+                className="w-10 h-10 text-blue-600"
+                fill="none"
+                stroke="currentColor"
+                viewBox="0 0 24 24"
+              >
+                <path
+                  strokeLinecap="round"
+                  strokeLinejoin="round"
+                  strokeWidth={2}
+                  d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2-2v6a2 2 0 002 2zm10-10V7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
+                />
+              </svg>
+            </div>
+            <h2 className="text-xl font-semibold text-gray-900 mb-2">
+              需要登录才能{action}
+            </h2>
+            <p className="text-gray-600 mb-6">
+              登录或注册以享受完整功能
+            </p>
+            <div className="space-y-3 max-w-xs mx-auto">
+              <button
+                onClick={() => navigate('/login')}
+                className="w-full bg-blue-600 text-white py-3 px-4 rounded-lg font-medium hover:bg-blue-700 transition-colors"
+              >
+                立即登录
+              </button>
+              <button
+                onClick={() => navigate('/register')}
+                className="w-full bg-white text-blue-600 py-3 px-4 rounded-lg font-medium border border-blue-600 hover:bg-blue-50 transition-colors"
+              >
+                注册账号
+              </button>
+            </div>
+          </div>
+        </div>
+      </>
+    );
+  }
+
+  return <>{children}</>;
+};

+ 94 - 0
src/client/mobile/components/GuestPrompt.tsx

@@ -0,0 +1,94 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useAuth } from '../hooks/AuthProvider';
+
+interface GuestPromptProps {
+  message?: string;
+  onLogin?: () => void;
+  onRegister?: () => void;
+}
+
+export const GuestPrompt: React.FC<GuestPromptProps> = ({
+  message = '请先登录或注册以继续访问',
+  onLogin,
+  onRegister,
+}) => {
+  const navigate = useNavigate();
+  const { isAuthenticated } = useAuth();
+
+  // 如果已认证,不显示提示
+  if (isAuthenticated) {
+    return null;
+  }
+
+  const handleLogin = () => {
+    if (onLogin) {
+      onLogin();
+    } else {
+      navigate('/login');
+    }
+  };
+
+  const handleRegister = () => {
+    if (onRegister) {
+      onRegister();
+    } else {
+      navigate('/register');
+    }
+  };
+
+  return (
+    <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
+      <div className="bg-white rounded-lg p-6 mx-4 max-w-sm w-full">
+        <div className="text-center">
+          <div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
+            <svg
+              className="w-8 h-8 text-blue-600"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                strokeLinecap="round"
+                strokeLinejoin="round"
+                strokeWidth={2}
+                d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2-2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
+              />
+            </svg>
+          </div>
+          
+          <h3 className="text-lg font-semibold text-gray-900 mb-2">
+            游客模式
+          </h3>
+          
+          <p className="text-sm text-gray-600 mb-6">
+            {message}
+          </p>
+          
+          <div className="space-y-3">
+            <button
+              onClick={handleLogin}
+              className="w-full bg-blue-600 text-white py-3 px-4 rounded-lg font-medium hover:bg-blue-700 transition-colors"
+            >
+              立即登录
+            </button>
+            
+            <button
+              onClick={handleRegister}
+              className="w-full bg-white text-blue-600 py-3 px-4 rounded-lg font-medium border border-blue-600 hover:bg-blue-50 transition-colors"
+            >
+              注册账号
+            </button>
+          </div>
+          
+          <button
+            onClick={() => window.history.back()}
+            className="mt-4 text-sm text-gray-500 hover:text-gray-700"
+          >
+            返回
+          </button>
+        </div>
+      </div>
+    </div>
+  );
+};

+ 11 - 7
src/client/mobile/components/ProtectedRoute.tsx

@@ -2,20 +2,24 @@ import React, { useEffect } from 'react';
 import { useNavigate } from 'react-router-dom';
 import { useAuth } from '../hooks/AuthProvider';
 
+interface ProtectedRouteProps {
+  children: React.ReactNode;
+  redirectTo?: string;
+}
 
-
-
-
-export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
+export const ProtectedRoute = ({ 
+  children, 
+  redirectTo = '/login'
+}: ProtectedRouteProps) => {
   const { isAuthenticated, isLoading } = useAuth();
   const navigate = useNavigate();
   
   useEffect(() => {
     // 只有在加载完成且未认证时才重定向
     if (!isLoading && !isAuthenticated) {
-      navigate('/login', { replace: true });
+      navigate(redirectTo, { replace: true });
     }
-  }, [isAuthenticated, isLoading, navigate]);
+  }, [isAuthenticated, isLoading, navigate, redirectTo]);
   
   // 显示加载状态,直到认证检查完成
   if (isLoading) {
@@ -31,5 +35,5 @@ export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
     return null;
   }
   
-  return children;
+  return <>{children}</>;
 };

+ 59 - 0
src/client/mobile/components/WithGuestPrompt.tsx

@@ -0,0 +1,59 @@
+import React, { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useAuth } from '../hooks/AuthProvider';
+import { GuestPrompt } from './GuestPrompt';
+
+interface WithGuestPromptProps {
+  requireAuth?: boolean;
+  customMessage?: string;
+}
+
+export const withGuestPrompt = <P extends object>(
+  WrappedComponent: React.ComponentType<P>,
+  options: WithGuestPromptProps = {}
+) => {
+  const { requireAuth = true, customMessage } = options;
+
+  return function ComponentWithGuestPrompt(props: P) {
+    const { isAuthenticated, isLoading } = useAuth();
+    const navigate = useNavigate();
+    const [showPrompt, setShowPrompt] = useState(false);
+
+    useEffect(() => {
+      if (!isLoading && requireAuth && !isAuthenticated) {
+        setShowPrompt(true);
+      }
+    }, [isAuthenticated, isLoading, requireAuth]);
+
+    if (isLoading) {
+      return (
+        <div className="flex justify-center items-center h-screen">
+          <div className="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-12 w-12"></div>
+        </div>
+      );
+    }
+
+    if (requireAuth && !isAuthenticated) {
+      return (
+        <>
+          <WrappedComponent {...props} />
+          {showPrompt && (
+            <GuestPrompt
+              message={customMessage || '请先登录或注册以继续浏览更多内容'}
+              onLogin={() => {
+                setShowPrompt(false);
+                navigate('/login');
+              }}
+              onRegister={() => {
+                setShowPrompt(false);
+                navigate('/register');
+              }}
+            />
+          )}
+        </>
+      );
+    }
+
+    return <WrappedComponent {...props} />;
+  };
+};

+ 28 - 0
src/client/mobile/hooks/useGuestMode.ts

@@ -0,0 +1,28 @@
+import { useState, useEffect } from 'react';
+import { useAuth } from './AuthProvider';
+
+interface UseGuestModeOptions {
+  requireAuth?: boolean;
+  customMessage?: string;
+}
+
+export const useGuestMode = (options: UseGuestModeOptions = {}) => {
+  const { requireAuth = false, customMessage } = options;
+  const { isAuthenticated, isLoading } = useAuth();
+  const [showPrompt, setShowPrompt] = useState(false);
+
+  useEffect(() => {
+    if (!isLoading && requireAuth && !isAuthenticated) {
+      setShowPrompt(true);
+    }
+  }, [isAuthenticated, isLoading, requireAuth]);
+
+  return {
+    isAuthenticated,
+    isLoading,
+    showPrompt,
+    setShowPrompt,
+    isGuest: !isAuthenticated,
+    message: customMessage || '请先登录或注册以继续访问'
+  };
+};