Initial TrueGrowth source import

This commit is contained in:
2026-07-07 09:36:36 +08:00
commit 3b6781d695
2283 changed files with 691996 additions and 0 deletions

790
docs/CODING_STANDARDS.md Normal file
View File

@@ -0,0 +1,790 @@
# Opentu 编码规范
本文档定义了Aitu项目的编码标准和最佳实践确保代码质量、可维护性和团队协作效率。
## 目录
- [总体原则](#总体原则)
- [文件结构与命名](#文件结构与命名)
- [TypeScript 规范](#typescript-规范)
- [React 组件规范](#react-组件规范)
- [CSS/SCSS 规范](#cssscss-规范)
- [测试规范](#测试规范)
- [Git 提交规范](#git-提交规范)
- [性能优化](#性能优化)
- [安全规范](#安全规范)
- [文档规范](#文档规范)
## 总体原则
### 代码哲学
- **简洁性**: 代码应该清晰、简洁、易于理解
- **一致性**: 保持整个项目的编码风格一致
- **可维护性**: 编写易于维护和扩展的代码
- **性能**: 在保证可读性的前提下追求性能
- **安全**: 始终考虑安全性,避免引入安全漏洞
### 文件大小限制
- **单个文件不超过500行** (包括空行和注释)
- 超过500行的文件应该进行合理拆分
- 例外情况需要在代码审查中说明理由
## 文件结构与命名
### 目录结构
```
packages/drawnix/src/
├── components/ # 可复用组件
│ ├── ui/ # 基础UI组件
│ ├── icons/ # 图标组件
│ └── feature/ # 功能性组件
├── hooks/ # 自定义 Hooks
├── utils/ # 工具函数
├── constants/ # 常量定义
├── types/ # TypeScript 类型定义
├── styles/ # 全局样式
├── plugins/ # Plait 插件
└── transforms/ # 数据变换逻辑
```
### 命名约定
#### 文件命名
- **组件文件**: `PascalCase.tsx` (如 `ImageCropPopup.tsx`)
- **Hook文件**: `camelCase.ts` (如 `useImageCrop.ts`)
- **工具文件**: `kebab-case.ts` (如 `image-utils.ts`)
- **类型文件**: `kebab-case.types.ts` (如 `image-crop.types.ts`)
- **常量文件**: `UPPER_SNAKE_CASE.ts` (如 `STORAGE_KEYS.ts`)
#### 变量命名
```typescript
// ✅ 推荐
const userName = 'alice';
const isLoading = true;
const API_BASE_URL = 'https://api.example.com';
const MAX_RETRY_COUNT = 3;
// ❌ 不推荐
const user_name = 'alice';
const loading = true;
const apiBaseUrl = 'https://api.example.com';
```
#### 组件命名
```typescript
// ✅ 推荐
const ImageCropPopup: React.FC<ImageCropPopupProps> = () => {};
const UserProfile = () => {};
// ❌ 不推荐
const imageCropPopup = () => {};
const userProfile = () => {};
```
## TypeScript 规范
### 类型定义
#### 接口定义
```typescript
// ✅ 推荐 - 使用 interface
interface User {
readonly id: string;
name: string;
email: string;
createdAt: Date;
avatar?: string; // 可选属性放在最后
}
// ✅ 推荐 - Props 接口
interface ImageCropPopupProps {
visible: boolean;
onClose: () => void;
onCropSelect: (shape: CropShape) => void;
children?: React.ReactNode;
}
```
#### 类型别名
```typescript
// ✅ 推荐 - 联合类型使用 type
type CropShape = 'rectangle' | 'circle' | 'ellipse';
type ButtonVariant = 'primary' | 'secondary' | 'outline';
// ✅ 推荐 - 复杂类型组合
type ApiResponse<T> = {
data: T;
status: 'success' | 'error';
message?: string;
};
```
#### 泛型使用
```typescript
// ✅ 推荐
interface Repository<T> {
findById(id: string): Promise<T | null>;
create(entity: Omit<T, 'id'>): Promise<T>;
update(id: string, updates: Partial<T>): Promise<T>;
}
// ✅ 推荐 - 泛型约束
interface Identifiable {
id: string;
}
function updateEntity<T extends Identifiable>(entity: T, updates: Partial<T>): T {
return { ...entity, ...updates };
}
```
### 严格模式配置
```json
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
```
## React 组件规范
### 组件结构
```typescript
// ✅ 推荐的组件结构
import React, { useState, useEffect, useCallback } from 'react';
import { Button } from 'tdesign-react';
import { useI18n } from '../../hooks/use-i18n';
import './ComponentName.scss';
// 1. 类型定义
interface ComponentProps {
title: string;
onAction: (data: string) => void;
disabled?: boolean;
}
// 2. 常量定义
const DEFAULT_CONFIG = {
timeout: 5000,
retryCount: 3,
};
// 3. 主组件
export const ComponentName: React.FC<ComponentProps> = ({
title,
onAction,
disabled = false,
}) => {
// 4. Hooks (固定顺序)
const { language } = useI18n();
const [isLoading, setIsLoading] = useState(false);
// 5. Effect hooks
useEffect(() => {
// 副作用逻辑
}, []);
// 6. 事件处理函数
const handleClick = useCallback(() => {
if (disabled) return;
onAction(title);
}, [disabled, onAction, title]);
// 7. 渲染逻辑
return (
<div className="component-name">
<Button onClick={handleClick} disabled={disabled || isLoading}>
{title}
</Button>
</div>
);
};
```
### Hooks 使用规范
#### 自定义 Hooks
```typescript
// ✅ 推荐
export const useImageCrop = (initialShape: CropShape = 'rectangle') => {
const [cropShape, setCropShape] = useState<CropShape>(initialShape);
const [isProcessing, setIsProcessing] = useState(false);
const applyCrop = useCallback(async (imageUrl: string) => {
setIsProcessing(true);
try {
// 裁剪逻辑
return processedImageUrl;
} finally {
setIsProcessing(false);
}
}, [cropShape]);
return {
cropShape,
setCropShape,
isProcessing,
applyCrop,
};
};
```
#### 依赖数组规范
```typescript
// ✅ 推荐 - 完整依赖
useEffect(() => {
fetchUserData(userId);
}, [userId, fetchUserData]);
// ✅ 推荐 - useCallback 优化
const fetchUserData = useCallback(async (id: string) => {
// 实现
}, []);
// ❌ 不推荐 - 缺少依赖
useEffect(() => {
fetchUserData(userId);
}, []); // 缺少 userId 依赖
```
### 状态管理
```typescript
// ✅ 推荐 - 简单状态
const [user, setUser] = useState<User | null>(null);
// ✅ 推荐 - 复杂状态使用 useReducer
interface FormState {
values: Record<string, any>;
errors: Record<string, string>;
isSubmitting: boolean;
}
type FormAction =
| { type: 'SET_FIELD'; field: string; value: any }
| { type: 'SET_ERROR'; field: string; error: string }
| { type: 'SET_SUBMITTING'; isSubmitting: boolean };
const formReducer = (state: FormState, action: FormAction): FormState => {
switch (action.type) {
case 'SET_FIELD':
return {
...state,
values: { ...state.values, [action.field]: action.value }
};
// 其他 case...
default:
return state;
}
};
```
## CSS/SCSS 规范
### 样式架构
```scss
// ✅ 推荐的组件样式结构
.image-crop-popup {
// 1. 位置相关
position: relative;
z-index: 999;
// 2. 盒模型
width: 300px;
padding: 16px;
margin: 0 auto;
// 3. 外观
background: var(--color-bg-container);
border: 1px solid var(--color-border);
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
// 4. 字体
font-family: var(--font-family);
color: var(--color-text);
// 5. 动画
transition: all 0.2s ease-out;
// 6. 嵌套选择器
&__header {
margin-bottom: 12px;
font-weight: 600;
}
&__content {
display: flex;
flex-direction: column;
gap: 8px;
}
// 7. 状态修饰符
&--visible {
opacity: 1;
transform: translateY(0);
}
&--hidden {
opacity: 0;
transform: translateY(-10px);
}
// 8. 响应式
@media (max-width: 768px) {
width: calc(100vw - 32px);
padding: 12px;
}
}
```
### 设计系统变量
```scss
// 使用设计系统定义的变量
:root {
// 品牌色彩
--brand-primary: #F39C12;
--brand-secondary: #5A4FCF;
--brand-accent: #E91E63;
// 渐变
--gradient-brand: linear-gradient(135deg, #F39C12 0%, #E67E22 30%, #5A4FCF 70%, #E91E63 100%);
--gradient-brush: linear-gradient(135deg, #5A4FCF 0%, #7B68EE 50%, #E91E63 100%);
// 间距
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
// 字体
--font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
--font-size-xs: 12px;
--font-size-sm: 14px;
--font-size-base: 16px;
--font-size-lg: 18px;
// 圆角
--radius-sm: 6px;
--radius-md: 8px;
--radius-lg: 12px;
// 阴影
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12);
--shadow-md: 0 4px 20px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.15);
}
```
### BEM 命名规范
```scss
// ✅ 推荐 - BEM 命名
.image-crop { // Block
&__popup { // Element
&--visible { // Modifier
opacity: 1;
}
}
&__button { // Element
&--primary { // Modifier
background: var(--gradient-brand);
}
&--disabled { // Modifier
opacity: 0.5;
cursor: not-allowed;
}
}
}
```
## 测试规范
### 单元测试
```typescript
// ✅ 推荐的测试结构
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { ImageCropPopup } from './ImageCropPopup';
describe('ImageCropPopup', () => {
const defaultProps = {
visible: true,
onClose: jest.fn(),
onCropSelect: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
});
describe('渲染行为', () => {
it('应该正确渲染裁剪选项', () => {
render(<ImageCropPopup {...defaultProps} />);
expect(screen.getByText('方形裁剪')).toBeInTheDocument();
expect(screen.getByText('圆形裁剪')).toBeInTheDocument();
expect(screen.getByText('椭圆形裁剪')).toBeInTheDocument();
});
it('当 visible 为 false 时应该隐藏', () => {
render(<ImageCropPopup {...defaultProps} visible={false} />);
expect(screen.queryByText('方形裁剪')).not.toBeInTheDocument();
});
});
describe('交互行为', () => {
it('点击裁剪选项应该调用 onCropSelect', async () => {
render(<ImageCropPopup {...defaultProps} />);
fireEvent.click(screen.getByText('圆形裁剪'));
await waitFor(() => {
expect(defaultProps.onCropSelect).toHaveBeenCalledWith('circle');
expect(defaultProps.onClose).toHaveBeenCalled();
});
});
});
});
```
### E2E 测试
```typescript
// ✅ Playwright E2E 测试示例
import { test, expect } from '@playwright/test';
test.describe('图片裁剪功能', () => {
test('用户可以成功裁剪图片', async ({ page }) => {
await page.goto('/');
// 上传图片
await page.setInputFiles('input[type="file"]', 'test-image.png');
// 选择图片
await page.click('[data-testid="uploaded-image"]');
// 打开裁剪菜单
await page.click('[data-testid="crop-button"]');
// 选择圆形裁剪
await page.click('text=圆形裁剪');
// 验证裁剪效果
const image = page.locator('[data-testid="cropped-image"]');
await expect(image).toHaveClass(/crop-circle/);
});
});
```
## Git 提交规范
### 提交消息格式
```
<type>(<scope>): <subject>
<body>
<footer>
```
### 类型说明
- `feat`: 新功能
- `fix`: Bug 修复
- `docs`: 文档更新
- `style`: 代码格式调整(不影响功能)
- `refactor`: 重构代码
- `test`: 添加或修改测试
- `chore`: 构建过程或辅助工具的变动
- `perf`: 性能优化
- `ci`: CI/CD 相关
### 示例
```bash
# ✅ 推荐
feat(crop): 添加图片圆形和椭圆形裁剪功能
- 实现三种裁剪形状:方形、圆形、椭圆形
- 添加裁剪预览功能
- 更新相关TypeScript类型定义
Closes #123
# ✅ 推荐
fix(api): 修复图片上传失败的问题
当文件大小超过5MB时上传会失败并显示错误消息
Fixes #456
# ❌ 不推荐
update stuff
fix bug
add feature
```
## 性能优化
### 代码分割
```typescript
// ✅ 推荐 - 路由级别的代码分割
const ImageEditor = React.lazy(() => import('./ImageEditor'));
const VideoEditor = React.lazy(() => import('./VideoEditor'));
// ✅ 推荐 - 组件级别的懒加载
const HeavyComponent = React.lazy(() =>
import('./HeavyComponent').then(module => ({
default: module.HeavyComponent
}))
);
```
### React 性能优化
```typescript
// ✅ 推荐 - React.memo 优化
const ExpensiveComponent = React.memo<Props>(({ data, onUpdate }) => {
return <div>{/* 渲染逻辑 */}</div>;
}, (prevProps, nextProps) => {
// 自定义比较逻辑
return prevProps.data.id === nextProps.data.id;
});
// ✅ 推荐 - useMemo 优化计算
const expensiveValue = useMemo(() => {
return computeExpensiveValue(data);
}, [data]);
// ✅ 推荐 - useCallback 优化函数
const handleClick = useCallback((id: string) => {
onItemClick(id);
}, [onItemClick]);
```
### 图片优化
```typescript
// ✅ 推荐 - 图片预加载
const preloadImage = (src: string): Promise<void> => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve();
img.onerror = reject;
img.src = src;
});
};
// ✅ 推荐 - 响应式图片
const ResponsiveImage: React.FC<{
src: string;
alt: string;
sizes: string;
}> = ({ src, alt, sizes }) => (
<img
src={src}
alt={alt}
sizes={sizes}
loading="lazy"
decoding="async"
/>
);
```
## 安全规范
### 输入验证
```typescript
// ✅ 推荐 - 严格的输入验证
const validateImageFile = (file: File): boolean => {
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];
const maxSize = 10 * 1024 * 1024; // 10MB
if (!allowedTypes.includes(file.type)) {
throw new Error('不支持的文件类型');
}
if (file.size > maxSize) {
throw new Error('文件大小超过限制');
}
return true;
};
// ✅ 推荐 - XSS 防护
const sanitizeInput = (input: string): string => {
return input
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;')
.replace(/\//g, '&#x2F;');
};
```
### API 安全
```typescript
// ✅ 推荐 - 安全的 API 调用
const apiClient = {
async request<T>(url: string, options: RequestInit = {}): Promise<T> {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
...options.headers,
},
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
};
// ✅ 推荐 - 敏感信息处理
const logSafeError = (error: Error, context?: Record<string, any>) => {
const safeContext = context ? {
...context,
apiKey: context.apiKey ? '[REDACTED]' : undefined,
password: context.password ? '[REDACTED]' : undefined,
} : {};
console.error('Application error:', {
message: error.message,
stack: error.stack,
context: safeContext,
});
};
```
### 环境变量管理
```typescript
// ✅ 推荐 - 类型安全的环境变量
interface EnvironmentConfig {
apiBaseUrl: string;
enableDebug: boolean;
geminiApiKey?: string;
}
const getEnvironmentConfig = (): EnvironmentConfig => {
const config: EnvironmentConfig = {
apiBaseUrl: process.env.VITE_API_BASE_URL || 'http://localhost:3000',
enableDebug: process.env.NODE_ENV === 'development',
geminiApiKey: process.env.VITE_GEMINI_API_KEY,
};
// 验证必需的环境变量
if (!config.apiBaseUrl) {
throw new Error('VITE_API_BASE_URL is required');
}
return config;
};
```
## 文档规范
### JSDoc 注释
```typescript
/**
* 图片裁剪工具类
* @example
* ```typescript
* const cropper = new ImageCropper();
* const result = await cropper.crop(imageUrl, 'circle');
* ```
*/
export class ImageCropper {
/**
* 裁剪图片
* @param imageUrl - 图片URL
* @param shape - 裁剪形状
* @param options - 裁剪选项
* @returns 裁剪后的图片URL
* @throws {Error} 当图片格式不支持时
*/
async crop(
imageUrl: string,
shape: CropShape,
options?: CropOptions
): Promise<string> {
// 实现逻辑
}
}
```
### README 模板
```markdown
# 组件名称
## 概述
简要描述组件的用途和功能。
## 安装
\`\`\`bash
npm install @aitu/component-name
\`\`\`
## 使用示例
\`\`\`typescript
import { ComponentName } from '@aitu/component-name';
const App = () => (
<ComponentName
prop1="value1"
onEvent={handleEvent}
/>
);
\`\`\`
## API 文档
### Props
| 属性名 | 类型 | 默认值 | 描述 |
|--------|------|--------|------|
| prop1 | string | - | 属性描述 |
| prop2 | boolean | false | 属性描述 |
### 事件
| 事件名 | 参数 | 描述 |
|--------|------|------|
| onEvent | (data: string) => void | 事件描述 |
## 开发指南
本地开发和测试说明。
```
## 代码审查清单
### 提交前检查
- [ ] 代码通过 TypeScript 类型检查
- [ ] 代码通过 ESLint 检查
- [ ] 代码通过单元测试
- [ ] 单个文件不超过 500 行
- [ ] 添加了必要的注释和文档
- [ ] 遵循了命名约定
- [ ] 没有硬编码的配置值
- [ ] 没有 console.log 等调试代码
- [ ] 安全性检查通过
### 性能检查
- [ ] 组件使用了适当的优化memo, useMemo, useCallback
- [ ] 图片资源进行了优化
- [ ] 避免了不必要的重新渲染
- [ ] 长列表使用了虚拟化
### 可访问性检查
- [ ] 添加了适当的 ARIA 标签
- [ ] 支持键盘导航
- [ ] 颜色对比度满足要求
- [ ] 支持屏幕阅读器
---
*本文档是活跃的,会随着项目发展持续更新。如有疑问或建议,请提交 Issue 或 PR。*