Initial TrueGrowth source import
This commit is contained in:
790
docs/CODING_STANDARDS.md
Normal file
790
docs/CODING_STANDARDS.md
Normal 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, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/\//g, '/');
|
||||
};
|
||||
```
|
||||
|
||||
### 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。*
|
||||
Reference in New Issue
Block a user