# 开发指南 本文档为开发者提供详细的项目架构说明、开发流程和最佳实践。 ## 目录 - [项目架构](#项目架构) - [技术栈详解](#技术栈详解) - [开发环境设置](#开发环境设置) - [代码规范](#代码规范) - [组件开发](#组件开发) - [状态管理](#状态管理) - [API 集成](#api-集成) - [测试策略](#测试策略) - [性能优化](#性能优化) - [调试技巧](#调试技巧) - [贡献流程](#贡献流程) ## 项目架构 ### 整体架构 ``` 三算命平台 ├── 前端应用 (React + TypeScript) │ ├── 用户界面层 │ ├── 业务逻辑层 │ ├── 数据访问层 │ └── 工具函数层 ├── 后端服务 (Supabase) │ ├── 数据库 (PostgreSQL) │ ├── 认证服务 │ ├── Edge Functions │ └── 实时订阅 └── 部署平台 (Vercel/Netlify) ├── CDN 加速 ├── 自动部署 └── 环境管理 ``` ### 前端架构 ``` src/ ├── components/ # 组件层 │ ├── ui/ # 基础UI组件 │ │ ├── Button.tsx │ │ ├── Card.tsx │ │ ├── Input.tsx │ │ └── Select.tsx │ ├── Layout.tsx # 布局组件 │ ├── AnalysisResultDisplay.tsx # 业务组件 │ └── ErrorBoundary.tsx # 错误边界 ├── pages/ # 页面层 │ ├── HomePage.tsx # 首页 │ ├── AnalysisPage.tsx # 分析页面 │ ├── HistoryPage.tsx # 历史记录 │ └── ProfilePage.tsx # 用户资料 ├── contexts/ # 状态管理层 │ └── AuthContext.tsx # 认证上下文 ├── hooks/ # 自定义Hook层 │ └── use-mobile.tsx # 移动端检测 ├── lib/ # 工具函数层 │ ├── supabase.ts # Supabase客户端 │ └── utils.ts # 通用工具 ├── types/ # 类型定义层 │ └── index.ts # TypeScript类型 └── data/ # 静态数据层 ``` ### 数据流架构 ``` 用户交互 → 组件状态 → Context/Hook → API调用 → Supabase → 数据库 ↓ ↓ ↓ ↓ ↓ ↓ UI更新 ← 状态更新 ← 数据处理 ← 响应处理 ← Edge Function ← 查询结果 ``` ## 技术栈详解 ### 前端核心技术 #### React 18.3.1 - **并发特性**: 使用 Suspense 和 lazy loading - **Hooks**: 优先使用函数组件和 Hooks - **错误边界**: 实现全局错误处理 ```typescript // 示例:使用 Suspense 进行代码分割 import { Suspense, lazy } from 'react' const AnalysisPage = lazy(() => import('./pages/AnalysisPage')) function App() { return ( 加载中...}> ) } ``` #### TypeScript - **严格模式**: 启用所有严格类型检查 - **类型定义**: 为所有 API 响应定义类型 - **泛型使用**: 提高代码复用性 ```typescript // 示例:API 响应类型定义 interface BaziAnalysisResult { bazi: { year: string month: string day: string hour: string } wuxing: { wood: number fire: number earth: number metal: number water: number } analysis: { character: string career: string wealth: string health: string relationships: string } } ``` #### Tailwind CSS - **实用优先**: 使用原子化 CSS 类 - **响应式设计**: 移动端优先的设计方法 - **自定义主题**: 中国风配色和字体 ```typescript // tailwind.config.js 自定义配置 module.exports = { theme: { extend: { colors: { 'chinese-red': '#DC143C', 'chinese-gold': '#FFD700', 'chinese-black': '#2C2C2C' }, fontFamily: { 'chinese': ['Noto Sans SC', 'sans-serif'] } } } } ``` ### 后端服务架构 #### Supabase - **数据库**: PostgreSQL 关系型数据库 - **认证**: JWT 基础的用户认证 - **实时**: WebSocket 实时数据同步 - **Edge Functions**: Deno 运行时的服务端函数 ```typescript // Supabase 客户端配置 import { createClient } from '@supabase/supabase-js' const supabaseUrl = import.meta.env.VITE_SUPABASE_URL const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY export const supabase = createClient(supabaseUrl, supabaseKey, { auth: { autoRefreshToken: true, persistSession: true, detectSessionInUrl: true } }) ``` ## 开发环境设置 ### 1. 环境要求 ```bash # 检查 Node.js 版本 node --version # >= 18.0.0 # 检查 pnpm 版本 pnpm --version # >= 8.0.0 # 检查 Git 版本 git --version # >= 2.0.0 ``` ### 2. 项目初始化 ```bash # 克隆项目 git clone https://github.com/patdelphi/suanming.git cd suanming # 安装依赖 pnpm install # 复制环境变量模板 cp .env.example .env.local # 编辑环境变量 vim .env.local ``` ### 3. 开发服务器 ```bash # 启动开发服务器 pnpm dev # 启动并打开浏览器 pnpm dev --open # 指定端口 pnpm dev --port 3000 ``` ### 4. 开发工具配置 #### VS Code 推荐扩展 ```json // .vscode/extensions.json { "recommendations": [ "bradlc.vscode-tailwindcss", "esbenp.prettier-vscode", "dbaeumer.vscode-eslint", "ms-vscode.vscode-typescript-next", "formulahendry.auto-rename-tag", "christian-kohler.path-intellisense" ] } ``` #### VS Code 设置 ```json // .vscode/settings.json { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "typescript.preferences.importModuleSpecifier": "relative", "tailwindCSS.experimental.classRegex": [ ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"], ["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] ] } ``` ## 代码规范 ### ESLint 配置 ```javascript // eslint.config.js import js from '@eslint/js' import globals from 'globals' import reactHooks from 'eslint-plugin-react-hooks' import reactRefresh from 'eslint-plugin-react-refresh' import tseslint from 'typescript-eslint' export default tseslint.config( { ignores: ['dist'] }, { extends: [js.configs.recommended, ...tseslint.configs.recommended], files: ['**/*.{ts,tsx}'], languageOptions: { ecmaVersion: 2020, globals: globals.browser, }, plugins: { 'react-hooks': reactHooks, 'react-refresh': reactRefresh, }, rules: { ...reactHooks.configs.recommended.rules, 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, ], '@typescript-eslint/no-unused-vars': 'warn', '@typescript-eslint/no-explicit-any': 'warn', }, }, ) ``` ### 命名规范 ```typescript // 组件命名:PascalCase const AnalysisResultDisplay: React.FC = () => {} // Hook 命名:camelCase,以 use 开头 const useAuth = () => {} // 常量命名:SCREAMING_SNAKE_CASE const API_BASE_URL = 'https://api.example.com' // 类型命名:PascalCase,接口以 I 开头(可选) interface UserProfile { id: string name: string } // 枚举命名:PascalCase enum AnalysisType { BAZI = 'bazi', ZIWEI = 'ziwei', YIJING = 'yijing' } ``` ### 文件组织规范 ```typescript // 导入顺序 // 1. React 相关 import React, { useState, useEffect } from 'react' // 2. 第三方库 import { useNavigate } from 'react-router-dom' import { toast } from 'sonner' // 3. 内部组件 import { Button } from '../ui/Button' import { Card } from '../ui/Card' // 4. 内部工具 import { supabase } from '../../lib/supabase' import { cn } from '../../lib/utils' // 5. 类型定义 import type { AnalysisResult } from '../../types' ``` ## 组件开发 ### 组件结构模板 ```typescript // components/ExampleComponent.tsx import React from 'react' import { cn } from '../../lib/utils' // 组件属性接口 interface ExampleComponentProps { title: string description?: string variant?: 'default' | 'primary' | 'secondary' className?: string children?: React.ReactNode onClick?: () => void } // 组件实现 const ExampleComponent: React.FC = ({ title, description, variant = 'default', className, children, onClick }) => { return (

{title}

{description &&

{description}

} {children}
) } export default ExampleComponent ``` ### UI 组件开发 使用 `class-variance-authority` 创建可变样式组件: ```typescript // components/ui/Button.tsx import { cva, type VariantProps } from 'class-variance-authority' import { cn } from '../../lib/utils' const buttonVariants = cva( 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background', { variants: { variant: { default: 'bg-primary text-primary-foreground hover:bg-primary/90', destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', outline: 'border border-input hover:bg-accent hover:text-accent-foreground', secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', link: 'underline-offset-4 hover:underline text-primary' }, size: { default: 'h-10 py-2 px-4', sm: 'h-9 px-3 rounded-md', lg: 'h-11 px-8 rounded-md' } }, defaultVariants: { variant: 'default', size: 'default' } } ) export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean } const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { return ( ) expect(screen.getByRole('button')).toBeInTheDocument() expect(screen.getByText('Click me')).toBeInTheDocument() }) it('should handle click events', () => { const handleClick = vi.fn() render() fireEvent.click(screen.getByRole('button')) expect(handleClick).toHaveBeenCalledTimes(1) }) it('should apply variant styles', () => { render() const button = screen.getByRole('button') expect(button).toHaveClass('bg-destructive') }) }) ``` ### E2E 测试 ```typescript // e2e/auth.spec.ts import { test, expect } from '@playwright/test' test.describe('Authentication', () => { test('should allow user to sign in', async ({ page }) => { await page.goto('/login') await page.fill('[data-testid="email"]', 'test@example.com') await page.fill('[data-testid="password"]', 'password123') await page.click('[data-testid="sign-in-button"]') await expect(page).toHaveURL('/dashboard') await expect(page.locator('[data-testid="user-menu"]')).toBeVisible() }) test('should show error for invalid credentials', async ({ page }) => { await page.goto('/login') await page.fill('[data-testid="email"]', 'invalid@example.com') await page.fill('[data-testid="password"]', 'wrongpassword') await page.click('[data-testid="sign-in-button"]') await expect(page.locator('[data-testid="error-message"]')).toBeVisible() }) }) ``` ## 性能优化 ### 代码分割 ```typescript // 路由级别的代码分割 import { lazy, Suspense } from 'react' import { Routes, Route } from 'react-router-dom' const HomePage = lazy(() => import('./pages/HomePage')) const AnalysisPage = lazy(() => import('./pages/AnalysisPage')) const HistoryPage = lazy(() => import('./pages/HistoryPage')) function App() { return ( 加载中...}> } /> } /> } /> ) } ``` ### 组件优化 ```typescript // 使用 React.memo 优化组件渲染 import React, { memo, useMemo, useCallback } from 'react' interface ExpensiveComponentProps { data: any[] onItemClick: (id: string) => void } const ExpensiveComponent = memo(({ data, onItemClick }) => { // 使用 useMemo 缓存计算结果 const processedData = useMemo(() => { return data.map(item => ({ ...item, processed: expensiveCalculation(item) })) }, [data]) // 使用 useCallback 缓存事件处理函数 const handleClick = useCallback((id: string) => { onItemClick(id) }, [onItemClick]) return (
{processedData.map(item => (
handleClick(item.id)}> {item.processed}
))}
) }) function expensiveCalculation(item: any) { // 模拟昂贵的计算 return item.value * Math.random() } ``` ### 图片优化 ```typescript // 图片懒加载组件 import React, { useState, useRef, useEffect } from 'react' interface LazyImageProps { src: string alt: string className?: string placeholder?: string } const LazyImage: React.FC = ({ src, alt, className, placeholder = '/placeholder.jpg' }) => { const [isLoaded, setIsLoaded] = useState(false) const [isInView, setIsInView] = useState(false) const imgRef = useRef(null) useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setIsInView(true) observer.disconnect() } }, { threshold: 0.1 } ) if (imgRef.current) { observer.observe(imgRef.current) } return () => observer.disconnect() }, []) return ( {alt} setIsLoaded(true)} style={{ opacity: isLoaded ? 1 : 0.5, transition: 'opacity 0.3s ease' }} /> ) } ``` ## 调试技巧 ### React DevTools ```typescript // 在开发环境中启用 React DevTools if (import.meta.env.DEV) { // 为组件添加显示名称 Component.displayName = 'ComponentName' // 添加调试信息 console.log('Component rendered with props:', props) } ``` ### 错误边界 ```typescript // components/ErrorBoundary.tsx import React, { Component, ErrorInfo, ReactNode } from 'react' interface Props { children: ReactNode fallback?: ReactNode } interface State { hasError: boolean error?: Error } export class ErrorBoundary extends Component { public state: State = { hasError: false } public static getDerivedStateFromError(error: Error): State { return { hasError: true, error } } public componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error('ErrorBoundary caught an error:', error, errorInfo) // 发送错误报告到监控服务 if (import.meta.env.PROD) { // Sentry.captureException(error, { extra: errorInfo }) } } public render() { if (this.state.hasError) { return this.props.fallback || (

出现了一些问题

请刷新页面重试

{import.meta.env.DEV && (
错误详情
{this.state.error?.stack}
)}
) } return this.props.children } } ``` ### 性能监控 ```typescript // lib/performance.ts export const measurePerformance = (name: string, fn: () => void) => { if (import.meta.env.DEV) { const start = performance.now() fn() const end = performance.now() console.log(`${name} took ${end - start} milliseconds`) } else { fn() } } // 使用示例 measurePerformance('Data Processing', () => { // 执行数据处理逻辑 processLargeDataSet(data) }) ``` ## 贡献流程 ### 1. 开发流程 ```bash # 1. 创建功能分支 git checkout -b feature/new-feature # 2. 开发和测试 npm run dev npm run test npm run lint # 3. 提交代码 git add . git commit -m "feat: add new feature" # 4. 推送分支 git push origin feature/new-feature # 5. 创建 Pull Request ``` ### 2. 提交信息规范 ``` type(scope): description [optional body] [optional footer] ``` 类型说明: - `feat`: 新功能 - `fix`: 修复bug - `docs`: 文档更新 - `style`: 代码格式调整 - `refactor`: 代码重构 - `test`: 测试相关 - `chore`: 构建过程或辅助工具的变动 ### 3. Code Review 检查清单 - [ ] 代码符合项目规范 - [ ] 包含适当的测试 - [ ] 文档已更新 - [ ] 性能影响已评估 - [ ] 安全性已考虑 - [ ] 向后兼容性已确认 - [ ] UI/UX 符合设计规范 --- 更多开发相关问题,请参考 [FAQ](FAQ.md) 或在 GitHub Issues 中讨论。