# 开发指南 本文档为开发者提供详细的项目架构说明、开发流程和最佳实践。 ## 目录 - [项目架构](#项目架构) - [技术栈详解](#技术栈详解) - [开发环境设置](#开发环境设置) - [代码规范](#代码规范) - [组件开发](#组件开发) - [状态管理](#状态管理) - [API 集成](#api-集成) - [测试策略](#测试策略) - [性能优化](#性能优化) - [调试技巧](#调试技巧) - [贡献流程](#贡献流程) ## 项目架构 ### 整体架构 ``` 神机阁平台 (v2.0 重构版) ├── 前端应用 (React + TypeScript) │ ├── 用户界面层 │ ├── 业务逻辑层 (分析与存储分离) │ ├── 数据访问层 (API去重机制) │ └── 工具函数层 ├── 后端服务 (Node.js + Express) │ ├── 数据库 (SQLite) │ ├── 认证中间件 (JWT) │ ├── 分析服务 (纯计算) │ ├── 历史记录服务 (专门存储) │ └── 路由层 └── 开发环境 ├── 本地开发服务器 ├── 热重载 └── 调试工具 ``` ### 前端架构 ``` src/ ├── components/ # 组件层 │ ├── ui/ # 基础UI组件 │ │ ├── Button.tsx │ │ ├── Card.tsx │ │ ├── Input.tsx │ │ └── Select.tsx │ ├── Layout.tsx # 布局组件 │ ├── AnalysisResultDisplay.tsx # 分析结果显示 │ ├── CompleteBaziAnalysis.tsx # 完整八字分析 │ ├── CompleteZiweiAnalysis.tsx # 完整紫微分析 │ ├── CompleteYijingAnalysis.tsx # 完整易经分析 │ ├── BaziAnalysisDisplay.tsx # 八字分析显示 │ ├── ProtectedRoute.tsx # 路由保护 │ └── ErrorBoundary.tsx # 错误边界 ├── pages/ # 页面层 │ ├── HomePage.tsx # 首页 │ ├── AnalysisPage.tsx # 分析页面 (重构) │ ├── HistoryPage.tsx # 历史记录 │ ├── ProfilePage.tsx # 用户资料 │ ├── LoginPage.tsx # 登录页面 │ ├── RegisterPage.tsx # 注册页面 │ ├── BaziDetailsPage.tsx # 八字详情 │ └── WuxingAnalysisPage.tsx # 五行分析 ├── contexts/ # 状态管理层 │ └── AuthContext.tsx # 认证上下文 ├── hooks/ # 自定义Hook层 │ └── use-mobile.tsx # 移动端检测 ├── lib/ # 工具函数层 │ ├── localApi.ts # 本地API客户端 (重构) │ └── utils.ts # 通用工具 ├── types/ # 类型定义层 │ └── index.ts # TypeScript类型 └── data/ # 静态数据层 ``` ### 数据流架构 (v2.0 重构版) ``` 分析流程: 用户交互 → 组件状态 → useMemo缓存 → 分析API → 分析服务 → 返回结果 ↓ ↓ ↓ ↓ ↓ ↓ 显示结果 ← 状态更新 ← 对象稳定化 ← 去重处理 ← 纯计算 ← 分析完成 ↓ 历史记录API → 存储服务 → SQLite数据库 ↓ ↓ ↓ 保存成功 ← 记录插入 ← 数据持久化 特点: - 分析与存储完全分离 - API请求去重机制 - 对象引用稳定化 - 错误隔离处理 ``` ## 架构重构 (v2.0) ### 重构背景 在v1.0版本中,我们遇到了以下问题: 1. **重复历史记录**:一次分析产生多条历史记录 2. **架构耦合**:分析计算与历史存储紧密耦合 3. **React StrictMode问题**:开发环境下useEffect重复执行 4. **对象引用不稳定**:每次渲染创建新对象导致重复渲染 ### 重构方案 #### 1. 分离关注点 ```typescript // 重构前:耦合架构 POST /analysis/bazi → 分析 + 存储 → 返回 { record_id, analysis } // 重构后:分离架构 POST /analysis/bazi → 纯分析 → 返回 { analysis } POST /analysis/save-history → 专门存储 → 返回 { record_id } ``` #### 2. 前端流程优化 ```typescript // 重构后的分析流程 async function handleAnalysis() { // 第一步:获取分析结果 const analysisResult = await localApi.analysis.bazi(birthData) setAnalysisResult(analysisResult.data.analysis) // 第二步:保存历史记录 try { await localApi.analysis.saveHistory('bazi', analysisResult.data.analysis, birthData) } catch (error) { // 历史记录保存失败不影响分析结果显示 console.warn('历史记录保存失败:', error) } } ``` #### 3. 性能优化措施 ```typescript // useMemo缓存对象,避免重复渲染 const memoizedBirthDate = useMemo(() => ({ date: formData.birth_date, time: formData.birth_time, name: formData.name, gender: formData.gender }), [formData.birth_date, formData.birth_time, formData.name, formData.gender]) // useEffect依赖优化 useEffect(() => { // 依赖具体字段而非整个对象 }, [birthDate?.date, birthDate?.time, birthDate?.name, birthDate?.gender]) // API请求去重 private pendingRequests: Map> = new Map() ``` #### 4. 错误处理改进 ```typescript // 容错机制:历史记录保存失败不影响分析功能 try { await saveHistory() } catch (historyError) { console.error('保存历史记录失败:', historyError) // 不抛出错误,不影响用户体验 } ``` ### 重构效果 | 指标 | 重构前 | 重构后 | 改善幅度 | |------|--------|--------|----------| | 重复记录数 | 3-5条/次 | 1条/次 | 减少80%+ | | API调用次数 | 多次重复 | 单次调用 | 减少60%+ | | 组件渲染次数 | 频繁重渲染 | 按需渲染 | 减少40%+ | | 代码可维护性 | 耦合严重 | 职责清晰 | 显著提升 | ## 技术栈详解 ### 前端核心技术 #### 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 中讨论。