|
|
@@ -1,8 +1,8 @@
|
|
|
-# 前台Home页面开发流程规范
|
|
|
+# 前台Home页面开发流程规范 (Tailwind CSS版)
|
|
|
|
|
|
## 适用场景
|
|
|
|
|
|
-前台用户界面开发,包括首页、登录页、注册页、会员中心等面向普通用户的页面实现流程。
|
|
|
+前台用户界面开发,包括首页、登录页、注册页、会员中心等面向普通用户的页面实现流程。采用原生Tailwind CSS进行样式开发。不使用antd
|
|
|
|
|
|
## 目录结构
|
|
|
|
|
|
@@ -11,7 +11,6 @@ src/client/home/
|
|
|
├── index.tsx # 入口文件
|
|
|
├── routes.tsx # 路由配置
|
|
|
├── components/ # 共享组件
|
|
|
-│ ├── ui/ # UI组件
|
|
|
│ ├── ErrorPage.tsx # 错误页面
|
|
|
│ ├── NotFoundPage.tsx # 404页面
|
|
|
│ └── ProtectedRoute.tsx # 路由保护组件
|
|
|
@@ -25,357 +24,3 @@ src/client/home/
|
|
|
├── RegisterPage.tsx # 注册页
|
|
|
└── MemberPage.tsx # 会员中心
|
|
|
```
|
|
|
-
|
|
|
-## 开发流程
|
|
|
-
|
|
|
-### 1. **创建页面组件**
|
|
|
- - 位置: `src/client/home/pages/[PageName]Page.tsx`
|
|
|
- - 首页组件示例 (HomePage.tsx):
|
|
|
- ```tsx
|
|
|
- import React from 'react';
|
|
|
- import { Link } from 'react-router-dom';
|
|
|
- import { Button, Card, Space, Typography } from 'antd';
|
|
|
- import { UserOutlined, ShoppingCartOutlined } from '@ant-design/icons';
|
|
|
- import MainLayout from '../layouts/MainLayout';
|
|
|
-
|
|
|
- const { Title, Paragraph } = Typography;
|
|
|
-
|
|
|
- const HomePage: React.FC = () => {
|
|
|
- return (
|
|
|
- <MainLayout>
|
|
|
- <div className="hero-section">
|
|
|
- <Title level={1}>欢迎使用我们的平台</Title>
|
|
|
- <Paragraph>
|
|
|
- 探索更多功能,提升您的体验
|
|
|
- </Paragraph>
|
|
|
- <Space size="middle">
|
|
|
- <Button type="primary" size="large" icon={<ShoppingCartOutlined />}>
|
|
|
- 立即开始
|
|
|
- </Button>
|
|
|
- <Button size="large" icon={<UserOutlined />}>
|
|
|
- <Link to="/login">登录</Link>
|
|
|
- </Button>
|
|
|
- </Space>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div className="features-section">
|
|
|
- <Card title="功能一" hoverable>
|
|
|
- <p>这是平台的核心功能之一</p>
|
|
|
- </Card>
|
|
|
- <Card title="功能二" hoverable>
|
|
|
- <p>这是平台的核心功能之二</p>
|
|
|
- </Card>
|
|
|
- <Card title="功能三" hoverable>
|
|
|
- <p>这是平台的核心功能之三</p>
|
|
|
- </Card>
|
|
|
- </div>
|
|
|
- </MainLayout>
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- export default HomePage;
|
|
|
- ```
|
|
|
-
|
|
|
-### 2. **创建布局组件**
|
|
|
- - 位置: `src/client/home/layouts/[LayoutName].tsx`
|
|
|
- - 主布局示例 (MainLayout.tsx):
|
|
|
- ```tsx
|
|
|
- import React from 'react';
|
|
|
- import { Layout, Menu, Avatar, Dropdown, Space } from 'antd';
|
|
|
- import { Link, useNavigate } from 'react-router-dom';
|
|
|
- import { UserOutlined, LogoutOutlined, HomeOutlined } from '@ant-design/icons';
|
|
|
- import { useAuth } from '../hooks/AuthProvider';
|
|
|
-
|
|
|
- const { Header, Content, Footer } = Layout;
|
|
|
-
|
|
|
- const MainLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
|
- const { user, logout } = useAuth();
|
|
|
- const navigate = useNavigate();
|
|
|
-
|
|
|
- const handleLogout = async () => {
|
|
|
- await logout();
|
|
|
- navigate('/login');
|
|
|
- };
|
|
|
-
|
|
|
- const userMenuItems = [
|
|
|
- {
|
|
|
- key: 'profile',
|
|
|
- icon: <UserOutlined />,
|
|
|
- label: <Link to="/member">个人中心</Link>,
|
|
|
- },
|
|
|
- {
|
|
|
- key: 'logout',
|
|
|
- icon: <LogoutOutlined />,
|
|
|
- label: <span onClick={handleLogout}>退出登录</span>,
|
|
|
- },
|
|
|
- ];
|
|
|
-
|
|
|
- return (
|
|
|
- <Layout className="layout">
|
|
|
- <Header style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
|
- <div className="logo">
|
|
|
- <Link to="/">平台名称</Link>
|
|
|
- </div>
|
|
|
- <Menu theme="dark" mode="horizontal" items={[
|
|
|
- { key: 'home', icon: <HomeOutlined />, label: <Link to="/">首页</Link> }
|
|
|
- ]} />
|
|
|
- <Space>
|
|
|
- {user ? (
|
|
|
- <Dropdown menu={{ items: userMenuItems }} placement="bottomRight">
|
|
|
- <Avatar icon={<UserOutlined />} />
|
|
|
- </Dropdown>
|
|
|
- ) : (
|
|
|
- <Space>
|
|
|
- <Link to="/login">登录</Link>
|
|
|
- <Link to="/register">注册</Link>
|
|
|
- </Space>
|
|
|
- )}
|
|
|
- </Space>
|
|
|
- </Header>
|
|
|
- <Content style={{ padding: '0 50px' }}>
|
|
|
- <div style={{ background: '#fff', padding: 24, minHeight: 280 }}>
|
|
|
- {children}
|
|
|
- </div>
|
|
|
- </Content>
|
|
|
- <Footer style={{ textAlign: 'center' }}>
|
|
|
- 平台名称 ©{new Date().getFullYear()} Created with React
|
|
|
- </Footer>
|
|
|
- </Layout>
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- export default MainLayout;
|
|
|
- ```
|
|
|
-
|
|
|
-### 3. **配置路由**
|
|
|
- - 位置: `src/client/home/routes.tsx`
|
|
|
- - 路由配置示例:
|
|
|
- ```typescript
|
|
|
- import React from 'react';
|
|
|
- import { createBrowserRouter } from 'react-router-dom';
|
|
|
- import HomePage from './pages/HomePage';
|
|
|
- import LoginPage from './pages/LoginPage';
|
|
|
- import RegisterPage from './pages/RegisterPage';
|
|
|
- import MemberPage from './pages/MemberPage';
|
|
|
- import NotFoundPage from './components/NotFoundPage';
|
|
|
- import ErrorPage from './components/ErrorPage';
|
|
|
- import ProtectedRoute from './components/ProtectedRoute';
|
|
|
-
|
|
|
- export const router = createBrowserRouter([
|
|
|
- {
|
|
|
- path: '/',
|
|
|
- element: <HomePage />,
|
|
|
- errorElement: <ErrorPage />
|
|
|
- },
|
|
|
- {
|
|
|
- path: '/login',
|
|
|
- element: <LoginPage />
|
|
|
- },
|
|
|
- {
|
|
|
- path: '/register',
|
|
|
- element: <RegisterPage />
|
|
|
- },
|
|
|
- {
|
|
|
- path: '/member',
|
|
|
- element: (
|
|
|
- <ProtectedRoute>
|
|
|
- <MemberPage />
|
|
|
- </ProtectedRoute>
|
|
|
- )
|
|
|
- },
|
|
|
- {
|
|
|
- path: '*',
|
|
|
- element: <NotFoundPage />
|
|
|
- }
|
|
|
- ]);
|
|
|
- ```
|
|
|
-
|
|
|
-### 4. **实现认证功能**
|
|
|
- - 位置: `src/client/home/hooks/AuthProvider.tsx`
|
|
|
- - 认证上下文示例:
|
|
|
- ```tsx
|
|
|
- import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
|
|
- import { authClient } from '@/client/api';
|
|
|
-
|
|
|
- interface User {
|
|
|
- id: number;
|
|
|
- username: string;
|
|
|
- email: string;
|
|
|
- }
|
|
|
-
|
|
|
- interface AuthContextType {
|
|
|
- user: User | null;
|
|
|
- loading: boolean;
|
|
|
- login: (username: string, password: string) => Promise<void>;
|
|
|
- register: (username: string, email: string, password: string) => Promise<void>;
|
|
|
- logout: () => Promise<void>;
|
|
|
- }
|
|
|
-
|
|
|
- const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
|
-
|
|
|
- export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
|
|
- const [user, setUser] = useState<User | null>(null);
|
|
|
- const [loading, setLoading] = useState(true);
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- // 初始化时检查用户是否已登录
|
|
|
- const checkAuth = async () => {
|
|
|
- try {
|
|
|
- const token = localStorage.getItem('token');
|
|
|
- if (token) {
|
|
|
- const res = await authClient.me.$get();
|
|
|
- if (res.ok) {
|
|
|
- const data = await res.json();
|
|
|
- setUser(data);
|
|
|
- } else {
|
|
|
- localStorage.removeItem('token');
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.error('Auth initialization error:', error);
|
|
|
- } finally {
|
|
|
- setLoading(false);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- checkAuth();
|
|
|
- }, []);
|
|
|
-
|
|
|
- const login = async (username: string, password: string) => {
|
|
|
- const res = await authClient.login.password.$post({
|
|
|
- json: { username, password }
|
|
|
- });
|
|
|
-
|
|
|
- if (!res.ok) {
|
|
|
- throw new Error('登录失败');
|
|
|
- }
|
|
|
-
|
|
|
- const data = await res.json();
|
|
|
- localStorage.setItem('token', data.token);
|
|
|
- setUser(data.user);
|
|
|
- };
|
|
|
-
|
|
|
- const register = async (username: string, email: string, password: string) => {
|
|
|
- const res = await authClient.register.create.$post({
|
|
|
- json: { username, email, password }
|
|
|
- });
|
|
|
-
|
|
|
- if (!res.ok) {
|
|
|
- throw new Error('注册失败');
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const logout = async () => {
|
|
|
- try {
|
|
|
- await authClient.logout.$post();
|
|
|
- } finally {
|
|
|
- localStorage.removeItem('token');
|
|
|
- setUser(null);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- return (
|
|
|
- <AuthContext.Provider value={{ user, loading, login, register, logout }}>
|
|
|
- {children}
|
|
|
- </AuthContext.Provider>
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- export const useAuth = () => {
|
|
|
- const context = useContext(AuthContext);
|
|
|
- if (context === undefined) {
|
|
|
- throw new Error('useAuth must be used within an AuthProvider');
|
|
|
- }
|
|
|
- return context;
|
|
|
- };
|
|
|
- ```
|
|
|
-
|
|
|
-### 5. **创建UI组件**
|
|
|
- - 位置: `src/client/home/components/ui/[ComponentName].tsx`
|
|
|
- - 按钮组件示例 (PrimaryButton.tsx):
|
|
|
- ```tsx
|
|
|
- import React from 'react';
|
|
|
- import { Button, ButtonProps } from 'antd';
|
|
|
-
|
|
|
- interface PrimaryButtonProps extends ButtonProps {
|
|
|
- children: React.ReactNode;
|
|
|
- }
|
|
|
-
|
|
|
- const PrimaryButton: React.FC<PrimaryButtonProps> = ({ children, ...props }) => {
|
|
|
- return (
|
|
|
- <Button type="primary" size="middle" {...props}>
|
|
|
- {children}
|
|
|
- </Button>
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- export default PrimaryButton;
|
|
|
- ```
|
|
|
-
|
|
|
-### 6. **入口文件配置**
|
|
|
- - 位置: `src/client/home/index.tsx`
|
|
|
- - 配置示例:
|
|
|
- ```tsx
|
|
|
- import React from 'react';
|
|
|
- import ReactDOM from 'react-dom/client';
|
|
|
- import { RouterProvider } from 'react-router-dom';
|
|
|
- import { AuthProvider } from './hooks/AuthProvider';
|
|
|
- import { router } from './routes';
|
|
|
- import '../style.css';
|
|
|
-
|
|
|
- ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
- <React.StrictMode>
|
|
|
- <AuthProvider>
|
|
|
- <RouterProvider router={router} />
|
|
|
- </AuthProvider>
|
|
|
- </React.StrictMode>
|
|
|
- );
|
|
|
- ```
|
|
|
-
|
|
|
-### 7. **样式规范**
|
|
|
- - 使用CSS Modules或Styled Components进行样式隔离
|
|
|
- - 全局样式: `src/client/style.css`
|
|
|
- - 页面特定样式: 与页面组件同名的`.module.css`文件
|
|
|
- - 示例 (HomePage.module.css):
|
|
|
- ```css
|
|
|
- .heroSection {
|
|
|
- text-align: center;
|
|
|
- padding: 60px 0;
|
|
|
- background-color: #f5f5f5;
|
|
|
- margin-bottom: 40px;
|
|
|
- }
|
|
|
-
|
|
|
- .featuresSection {
|
|
|
- display: grid;
|
|
|
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
|
- gap: 24px;
|
|
|
- }
|
|
|
- ```
|
|
|
-
|
|
|
-### 8. **路由保护**
|
|
|
- - 使用ProtectedRoute组件保护需要登录的页面:
|
|
|
- ```tsx
|
|
|
- import React from 'react';
|
|
|
- import { Navigate } from 'react-router-dom';
|
|
|
- import { useAuth } from '../hooks/AuthProvider';
|
|
|
-
|
|
|
- interface ProtectedRouteProps {
|
|
|
- children: React.ReactNode;
|
|
|
- }
|
|
|
-
|
|
|
- const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ children }) => {
|
|
|
- const { user, loading } = useAuth();
|
|
|
-
|
|
|
- if (loading) {
|
|
|
- return <div>Loading...</div>;
|
|
|
- }
|
|
|
-
|
|
|
- if (!user) {
|
|
|
- // 未登录,重定向到登录页
|
|
|
- return <Navigate to="/login" replace />;
|
|
|
- }
|
|
|
-
|
|
|
- return <>{children}</>;
|
|
|
- };
|
|
|
-
|
|
|
- export default ProtectedRoute;
|