diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 239faf3..b1c8176 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,97 +1,934 @@ # 贡献指南 (Contributing Guide) -感谢你对 **KVideo** 项目感兴趣!我们非常欢迎并感谢社区的任何贡献,无论是修复 Bug、改进文档,还是提出新功能建议。 +欢迎来到 **KVideo** 项目!我们非常感谢你愿意为这个项目做出贡献。无论是修复 Bug、添加新功能、改进文档,还是提出建议,你的每一份贡献都将让这个项目变得更好。 -为了确保协作顺畅,请在提交贡献前花几分钟阅读以下指南。 +为了确保协作顺畅、代码质量一致,请在提交贡献前仔细阅读本指南。 + +## 📋 目录 + +- [行为准则](#行为准则) +- [快速开始](#快速开始) +- [开发环境设置](#开发环境设置) +- [代码规范](#代码规范) +- [Git 工作流程](#git-工作流程) +- [提交规范](#提交规范) +- [Pull Request 指南](#pull-request-指南) +- [设计系统规范](#设计系统规范) +- [测试要求](#测试要求) +- [常见问题](#常见问题) ## 🤝 行为准则 -我们致力于构建一个开放、友好、包容的社区。请在参与项目时保持尊重和礼貌。 +我们致力于构建一个开放、友好、包容的社区环境。请在参与项目时: -## 🛠 开发流程 +- ✅ 保持尊重和礼貌 +- ✅ 欢迎不同的观点和经验 +- ✅ 接受建设性的批评 +- ✅ 专注于对社区最有利的事情 +- ❌ 不要使用性别化的语言或图像 +- ❌ 不要进行人身攻击或政治攻击 +- ❌ 不要骚扰或歧视他人 -### 1. Fork 项目 +详细的行为准则请参阅 [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)。 -首先,将本仓库 Fork 到你自己的 GitHub 账号下。 +## 🚀 快速开始 -### 2. 克隆到本地 +### 我能贡献什么? + +以下是一些你可以做出贡献的方式: + +1. **🐛 报告 Bug**:发现了问题?请提交 Issue +2. **💡 提出新功能**:有好想法?在 Discussions 或 Issues 中分享 +3. **📝 改进文档**:发现文档不清晰或有错误?帮助我们改进 +4. **🎨 优化 UI/UX**:让界面更美观、更易用 +5. **⚡ 性能优化**:让应用运行得更快 +6. **🔧 修复 Bug**:解决现有的问题 +7. **✨ 添加功能**:实现新的特性 + +### 第一次贡献? + +如果这是你第一次为开源项目做贡献,我们推荐: + +1. 浏览 [GitHub Issues](https://github.com/KuekHaoYang/KVideo/issues) +2. 寻找标记为 `good first issue` 的问题 +3. 在 Issue 中评论,表明你想要解决这个问题 +4. 按照本指南进行开发和提交 + +## 🛠 开发环境设置 + +### 系统要求 + +确保你的开发环境满足以下要求: + +| 工具 | 最低版本 | 推荐版本 | 检查命令 | +|------|----------|----------|----------| +| **Node.js** | 20.0.0 | 20.x LTS | `node --version` | +| **npm** | 9.0.0 | 10.x | `npm --version` | +| **Git** | 2.30.0 | 最新版本 | `git --version` | + +### 详细设置步骤 + +#### 1. Fork 仓库 + +点击 GitHub 页面右上角的 "Fork" 按钮,将项目 Fork 到你的账号下。 + +#### 2. 克隆仓库 ```bash -git clone https://github.com/KuekHaoYang/KVideo.git +# 克隆你 Fork 的仓库 +git clone https://github.com/YOUR_USERNAME/KVideo.git cd KVideo + +# 添加上游仓库 +git remote add upstream https://github.com/KuekHaoYang/KVideo.git ``` -### 3. 创建分支 - -请基于 `main` 分支创建新的功能分支。分支命名建议遵循以下规范: - -* `feat/功能名称`: 新功能 (例如: `feat/add-subtitle-support`) -* `fix/问题描述`: 修复 Bug (例如: `fix/search-bar-layout`) -* `docs/文档修改`: 文档更新 (例如: `docs/update-readme`) -* `refactor/重构`: 代码重构 (例如: `refactor/api-client`) +#### 3. 安装依赖 ```bash -git checkout -b feat/your-feature-name +npm install ``` -### 4. 开发与调试 - -请确保你的代码符合项目的技术栈和风格: - -* **TypeScript**: 请尽量使用强类型,避免使用 `any`。 -* **Tailwind CSS**: 使用 Tailwind 类名进行样式开发,保持 "Liquid Glass" 的设计风格。 -* **组件化**: 保持组件的单一职责,复用 `components/ui` 下的基础组件。 - -启动本地开发服务器: +#### 4. 启动开发服务器 ```bash npm run dev ``` -### 5. 提交代码 (Commit) +访问 `http://localhost:3000` 查看应用。 -我们推荐使用 [Conventional Commits](https://www.conventionalcommits.org/) 规范来编写提交信息: +#### 5. 验证环境 -* `feat`: 新功能 -* `fix`: 修复 Bug -* `docs`: 文档变更 -* `style`: 代码格式 (不影响代码运行的变动) -* `refactor`: 重构 (既不是新增功能,也不是修改 bug 的代码变动) -* `perf`: 性能优化 -* `test`: 增加测试 -* `chore`: 构建过程或辅助工具的变动 - -示例: -```bash -git commit -m "feat: 增加视频倍速播放功能" -``` - -### 6. 提交 Pull Request (PR) - -1. 将代码推送到你的远程仓库:`git push origin feat/your-feature-name` -2. 在 GitHub 上发起 Pull Request 到 `KVideo:main` 分支。 -3. 请在 PR 描述中详细说明你的修改内容、解决的问题以及测试截图(如果是 UI 变更)。 - -## 📐 代码风格 - -本项目使用 ESLint 和 Prettier 进行代码检查和格式化。在提交代码前,请确保通过了代码检查: +确保以下命令都能正常运行: ```bash +# 代码检查 npm run lint + +# 构建测试 +npm run build ``` -## 🐛 发现 Bug? +## 📏 代码规范 -如果你发现了 Bug,请在 GitHub Issues 中提交报告。提交时请包含以下信息: +### 核心规范 -* Bug 的详细描述 -* 复现步骤 -* 预期的行为 -* 截图或报错日志 (如果有) -* 你的运行环境 (浏览器版本, OS 等) +#### 1. 文件长度限制 ⚠️ -## 💡 提出新功能? +> [!CAUTION] +> **这是项目的硬性规则!所有项目文件必须保持在 150 行以内(除系统文件外)。** -如果你有新的想法,欢迎在 Issues 中提出 Feature Request。请详细描述该功能的用途和你的实现思路。 +**检查命令:** -再次感谢你的贡献!让我们一起把 KVideo 变得更好! +```bash +find . -type f -not -path "*/node_modules/*" -not -path "*/.next/*" -not -path "*/.git/*" -not -name "package-lock.json" -not -name "*.png" -not -name "*.md" | xargs wc -l | awk '$1 > 150 && $2 != "total" {print $2 " - " $1 "行"}' +``` + +**如果命令有输出,说明有文件超过 150 行,必须重构!** + +**重构策略:** + +如果文件超过 150 行,请使用以下方法重构: + +##### A. 提取组件 + +**问题:** 一个组件太长,包含太多 JSX + +**解决方案:** 将大组件拆分为多个小组件 + +```typescript +// ❌ 不好:一个 200 行的大组件 +export function VideoPlayer() { + // 150+ 行代码 + return ( +
+ {/* 大量 JSX */} +
+ ); +} + +// ✅ 好:拆分为多个小组件 +export function VideoPlayer() { + return ( +
+ + + +
+ ); +} + +// PlayerControls.tsx (单独文件) +export function PlayerControls() { /* ... */ } + +// ProgressBar.tsx (单独文件) +export function ProgressBar() { /* ... */ } + +// VolumeControl.tsx (单独文件) +export function VolumeControl() { /* ... */ } +``` + +##### B. 提取自定义 Hook + +**问题:** 组件包含大量状态逻辑 + +**解决方案:** 将逻辑提取到自定义 Hook + +```typescript +// ❌ 不好:组件内有大量状态逻辑 +export function SearchPage() { + const [query, setQuery] = useState(''); + const [results, setResults] = useState([]); + const [loading, setLoading] = useState(false); + // ... 大量逻辑 + + const handleSearch = async () => { + // ... 50+ 行逻辑 + }; + + return
{/* JSX */}
; +} + +// ✅ 好:提取到自定义 Hook +export function SearchPage() { + const { query, results, loading, handleSearch } = useSearch(); + return
{/* JSX */}
; +} + +// useSearch.ts (单独文件) +export function useSearch() { + // ... 所有状态逻辑 + return { query, results, loading, handleSearch }; +} +``` + +##### C. 提取工具函数 + +**问题:** 文件包含大量辅助函数 + +**解决方案:** 将工具函数移到 `lib/utils/` + +```typescript +// ❌ 不好:组件文件包含工具函数 +export function VideoCard() { + const formatDuration = (seconds: number) => { + // ... 格式化逻辑 + }; + + const formatDate = (date: Date) => { + // ... 格式化逻辑 + }; + + // ... 更多工具函数 + + return
{/* JSX */}
; +} + +// ✅ 好:提取到工具文件 +import { formatDuration, formatDate } from '@/lib/utils/format-utils'; + +export function VideoCard() { + return
{/* JSX */}
; +} + +// lib/utils/format-utils.ts +export function formatDuration(seconds: number) { /* ... */ } +export function formatDate(date: Date) { /* ... */ } +``` + +##### D. 模块化 + +**问题:** 单个文件处理多个相关功能 + +**解决方案:** 按功能拆分文件并使用桶文件(barrel exports) + +```typescript +// ❌ 不好:player-utils.ts 包含 200 行 +export function parseHLS() { /* ... */ } +export function handlePlayback() { /* ... */ } +export function manageQuality() { /* ... */ } +// ... 更多函数 + +// ✅ 好:拆分为多个文件 +// lib/utils/player/index.ts +export * from './hls-parser'; +export * from './playback-manager'; +export * from './quality-manager'; + +// lib/utils/player/hls-parser.ts +export function parseHLS() { /* ... */ } + +// lib/utils/player/playback-manager.ts +export function handlePlayback() { /* ... */ } + +// lib/utils/player/quality-manager.ts +export function manageQuality() { /* ... */ } +``` + +#### 2. TypeScript 规范 + +**类型安全** + +```typescript +// ❌ 避免使用 any +function processData(data: any) { + return data.value; +} + +// ✅ 使用具体类型 +interface VideoData { + id: string; + title: string; + url: string; +} + +function processData(data: VideoData) { + return data.title; +} + +// ✅ 或使用 unknown(需要类型检查) +function processData(data: unknown) { + if (typeof data === 'object' && data !== null && 'value' in data) { + return (data as { value: string }).value; + } + throw new Error('Invalid data'); +} +``` + +**函数返回类型** + +```typescript +// ❌ 缺少返回类型 +function calculateTotal(items) { + return items.reduce((sum, item) => sum + item.price, 0); +} + +// ✅ 明确返回类型 +function calculateTotal(items: Item[]): number { + return items.reduce((sum, item) => sum + item.price, 0); +} +``` + +**接口定义** + +```typescript +// ✅ 使用 interface 定义对象类型 +interface VideoCardProps { + video: Video; + onPlay: (id: string) => void; + className?: string; +} + +// ✅ 使用 type 定义联合类型 +type ThemeMode = 'light' | 'dark' | 'system'; +``` + +#### 3. React 组件规范 + +**函数组件** + +```typescript +// ✅ 标准函数组件结构 +interface ButtonProps { + variant?: 'primary' | 'secondary'; + children: React.ReactNode; + onClick?: () => void; +} + +export function Button({ variant = 'primary', children, onClick }: ButtonProps) { + return ( + + ); +} +``` + +**组件文件组织** + +```typescript +// 1. 导入 +import React from 'react'; +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; + +// 2. 类型定义 +interface ComponentProps { + // ... +} + +// 3. 组件定义 +export function Component({ prop1, prop2 }: ComponentProps) { + // 4. Hooks + const [state, setState] = useState(); + const router = useRouter(); + + // 5. 事件处理函数 + const handleClick = () => { + // ... + }; + + // 6. 渲染 + return ( +
{/* JSX */}
+ ); +} +``` + +**单一职责原则** + +```typescript +// ❌ 组件做太多事情 +export function VideoSection() { + // 获取数据 + // 处理搜索 + // 渲染列表 + // 处理分页 + // 处理过滤 +} + +// ✅ 拆分为专注的组件 +export function VideoSection() { + const videos = useVideos(); + return ( +
+ + + + +
+ ); +} +``` + +#### 4. 样式规范 + +**Tailwind CSS 优先** + +```typescript +// ✅ 使用 Tailwind 类名 +export function Card({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} +``` + +**遵循 Liquid Glass 设计系统** + +```typescript +// ✅ 正确使用圆角 +
{/* 容器:大圆角 */} +
{/* 小元素:完全圆形 */} + +// ❌ 不要使用其他圆角值 +
{/* 错误! */} +
{/* 错误! */} +``` + +**响应式设计** + +```typescript +// ✅ 移动优先的响应式设计 +
+``` + +#### 5. 命名规范 + +**文件命名** + +- 组件文件:`PascalCase.tsx`(例如:`VideoCard.tsx`) +- Hook 文件:`camelCase.ts`(例如:`useVideoPlayer.ts`) +- 工具文件:`kebab-case.ts`(例如:`format-utils.ts`) +- 类型文件:`kebab-case.ts`(例如:`video-types.ts`) + +**变量命名** + +```typescript +// ✅ 清晰的命名 +const videoList = [...]; +const isLoading = false; +const handleSubmit = () => {}; + +// ❌ 模糊的命名 +const data = [...]; +const flag = false; +const fn = () => {}; +``` + +**常量命名** + +```typescript +// ✅ 全大写 + 下划线 +const MAX_VIDEO_DURATION = 7200; +const API_BASE_URL = 'https://api.example.com'; +``` + +#### 6. 导入顺序 + +```typescript +// 1. React 和 Next.js +import React from 'react'; +import { useState } from 'react'; +import Link from 'next/link'; + +// 2. 第三方库 +import { create } from 'zustand'; + +// 3. 项目别名导入 +import { Button } from '@/components/ui/Button'; +import { formatDate } from '@/lib/utils/date-utils'; + +// 4. 相对路径导入 +import { LocalComponent } from './LocalComponent'; + +// 5. 类型导入 +import type { Video } from '@/lib/types/video'; +``` + +## 🔄 Git 工作流程 + +### 分支策略 + +**主分支** + +- `main`:稳定的生产分支,只接受 PR 合并 + +**功能分支命名** + +遵循以下命名规范: + +- `feat/功能名称`:新功能(例如:`feat/add-playlist`) +- `fix/问题描述`:错误修复(例如:`fix/search-crash`) +- `docs/文档修改`:文档更新(例如:`docs/update-readme`) +- `refactor/重构名称`:代码重构(例如:`refactor/player-controls`) +- `perf/优化内容`:性能优化(例如:`perf/image-loading`) +- `style/样式修改`:样式调整(例如:`style/button-spacing`) +- `test/测试内容`:测试相关(例如:`test/add-unit-tests`) +- `chore/其他修改`:构建或工具变动(例如:`chore/update-deps`) + +### 开发流程 + +#### 1. 同步上游仓库 + +在开始新工作前,先同步最新的代码: + +```bash +# 获取上游更新 +git fetch upstream + +# 切换到主分支 +git checkout main + +# 合并上游更新 +git merge upstream/main + +# 推送到你的 Fork +git push origin main +``` + +#### 2. 创建功能分支 + +```bash +# 从 main 创建新分支 +git checkout -b feat/your-feature-name + +# 确认当前分支 +git branch +``` + +#### 3. 进行开发 + +在开发过程中: + +- 频繁提交小的、原子性的改动 +- 编写清晰的提交信息 +- 定期运行 `npm run lint` 检查代码 + +#### 4. 提交前检查 + +**必须通过的检查:** + +```bash +# 1. 代码规范检查 +npm run lint + +# 2. 文件长度检查 +find . -type f -not -path "*/node_modules/*" -not -path "*/.next/*" -not -path "*/.git/*" -not -name "package-lock.json" -not -name "*.png" -not -name "*.md" | xargs wc -l | awk '$1 > 150 && $2 != "total" {print $2 " - " $1 "行"}' + +# 3. 构建测试 +npm run build +``` + +**如果任何检查失败,必须先修复!** + +#### 5. 推送分支 + +```bash +# 推送到你的 Fork +git push origin feat/your-feature-name +``` + +## 📝 提交规范 + +### Conventional Commits + +我们使用 [Conventional Commits](https://www.conventionalcommits.org/) 规范: + +``` +(): + + + +