Skip to content

Latest commit

 

History

History
934 lines (672 loc) · 20.4 KB

File metadata and controls

934 lines (672 loc) · 20.4 KB

贡献指南 (Contributing Guide)

欢迎来到 KVideo 项目!我们非常感谢你愿意为这个项目做出贡献。无论是修复 Bug、添加新功能、改进文档,还是提出建议,你的每一份贡献都将让这个项目变得更好。

为了确保协作顺畅、代码质量一致,请在提交贡献前仔细阅读本指南。

📋 目录

🤝 行为准则

我们致力于构建一个开放、友好、包容的社区环境。请在参与项目时:

  • ✅ 保持尊重和礼貌
  • ✅ 欢迎不同的观点和经验
  • ✅ 接受建设性的批评
  • ✅ 专注于对社区最有利的事情
  • ❌ 不要使用性别化的语言或图像
  • ❌ 不要进行人身攻击或政治攻击
  • ❌ 不要骚扰或歧视他人

详细的行为准则请参阅 CODE_OF_CONDUCT.md

🚀 快速开始

我能贡献什么?

以下是一些你可以做出贡献的方式:

  1. 🐛 报告 Bug:发现了问题?请提交 Issue
  2. 💡 提出新功能:有好想法?在 Discussions 或 Issues 中分享
  3. 📝 改进文档:发现文档不清晰或有错误?帮助我们改进
  4. 🎨 优化 UI/UX:让界面更美观、更易用
  5. ⚡ 性能优化:让应用运行得更快
  6. 🔧 修复 Bug:解决现有的问题
  7. ✨ 添加功能:实现新的特性

第一次贡献?

如果这是你第一次为开源项目做贡献,我们推荐:

  1. 浏览 GitHub 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. 克隆仓库

# 克隆你 Fork 的仓库
git clone https://github.com/YOUR_USERNAME/KVideo.git
cd KVideo

# 添加上游仓库
git remote add upstream https://github.com/KuekHaoYang/KVideo.git

3. 安装依赖

npm install

4. 启动开发服务器

npm run dev

访问 http://localhost:3000 查看应用。

5. 验证环境

确保以下命令都能正常运行:

# 代码检查
npm run lint

# 构建测试
npm run build

📏 代码规范

核心规范

1. 文件长度限制 ⚠️

Caution

这是项目的硬性规则!所有项目文件必须保持在 150 行以内(除系统文件外)。

检查命令:

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

解决方案: 将大组件拆分为多个小组件

// ❌ 不好:一个 200 行的大组件
export function VideoPlayer() {
  // 150+ 行代码
  return (
    <div>
      {/* 大量 JSX */}
    </div>
  );
}

// ✅ 好:拆分为多个小组件
export function VideoPlayer() {
  return (
    <div>
      <PlayerControls />
      <ProgressBar />
      <VolumeControl />
    </div>
  );
}

// PlayerControls.tsx (单独文件)
export function PlayerControls() { /* ... */ }

// ProgressBar.tsx (单独文件)
export function ProgressBar() { /* ... */ }

// VolumeControl.tsx (单独文件)
export function VolumeControl() { /* ... */ }
B. 提取自定义 Hook

问题: 组件包含大量状态逻辑

解决方案: 将逻辑提取到自定义 Hook

// ❌ 不好:组件内有大量状态逻辑
export function SearchPage() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  // ... 大量逻辑
  
  const handleSearch = async () => {
    // ... 50+ 行逻辑
  };
  
  return <div>{/* JSX */}</div>;
}

// ✅ 好:提取到自定义 Hook
export function SearchPage() {
  const { query, results, loading, handleSearch } = useSearch();
  return <div>{/* JSX */}</div>;
}

// useSearch.ts (单独文件)
export function useSearch() {
  // ... 所有状态逻辑
  return { query, results, loading, handleSearch };
}
C. 提取工具函数

问题: 文件包含大量辅助函数

解决方案: 将工具函数移到 lib/utils/

// ❌ 不好:组件文件包含工具函数
export function VideoCard() {
  const formatDuration = (seconds: number) => {
    // ... 格式化逻辑
  };
  
  const formatDate = (date: Date) => {
    // ... 格式化逻辑
  };
  
  // ... 更多工具函数
  
  return <div>{/* JSX */}</div>;
}

// ✅ 好:提取到工具文件
import { formatDuration, formatDate } from '@/lib/utils/format-utils';

export function VideoCard() {
  return <div>{/* JSX */}</div>;
}

// lib/utils/format-utils.ts
export function formatDuration(seconds: number) { /* ... */ }
export function formatDate(date: Date) { /* ... */ }
D. 模块化

问题: 单个文件处理多个相关功能

解决方案: 按功能拆分文件并使用桶文件(barrel exports)

// ❌ 不好: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 规范

类型安全

// ❌ 避免使用 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');
}

函数返回类型

// ❌ 缺少返回类型
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);
}

接口定义

// ✅ 使用 interface 定义对象类型
interface VideoCardProps {
  video: Video;
  onPlay: (id: string) => void;
  className?: string;
}

// ✅ 使用 type 定义联合类型
type ThemeMode = 'light' | 'dark' | 'system';

3. React 组件规范

函数组件

// ✅ 标准函数组件结构
interface ButtonProps {
  variant?: 'primary' | 'secondary';
  children: React.ReactNode;
  onClick?: () => void;
}

export function Button({ variant = 'primary', children, onClick }: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

组件文件组织

// 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 (
    <div>{/* JSX */}</div>
  );
}

单一职责原则

// ❌ 组件做太多事情
export function VideoSection() {
  // 获取数据
  // 处理搜索
  // 渲染列表
  // 处理分页
  // 处理过滤
}

// ✅ 拆分为专注的组件
export function VideoSection() {
  const videos = useVideos();
  return (
    <div>
      <SearchBar />
      <FilterPanel />
      <VideoList videos={videos} />
      <Pagination />
    </div>
  );
}

4. 样式规范

Tailwind CSS 优先

// ✅ 使用 Tailwind 类名
export function Card({ children }: { children: React.ReactNode }) {
  return (
    <div className="rounded-2xl glass p-6 hover:shadow-lg transition-shadow">
      {children}
    </div>
  );
}

遵循 Liquid Glass 设计系统

// ✅ 正确使用圆角
<div className="rounded-2xl">  {/* 容器:大圆角 */}
<div className="rounded-full">  {/* 小元素:完全圆形 */}

// ❌ 不要使用其他圆角值
<div className="rounded-lg">   {/* 错误! */}
<div className="rounded-xl">   {/* 错误! */}

响应式设计

// ✅ 移动优先的响应式设计
<div className="
  flex flex-col           {/* 移动端:垂直布局 */}
  md:flex-row            {/* 平板及以上:水平布局 */}
  gap-4 md:gap-6         {/* 响应式间距 */}
">

5. 命名规范

文件命名

  • 组件文件:PascalCase.tsx(例如:VideoCard.tsx
  • Hook 文件:camelCase.ts(例如:useVideoPlayer.ts
  • 工具文件:kebab-case.ts(例如:format-utils.ts
  • 类型文件:kebab-case.ts(例如:video-types.ts

变量命名

// ✅ 清晰的命名
const videoList = [...];
const isLoading = false;
const handleSubmit = () => {};

// ❌ 模糊的命名
const data = [...];
const flag = false;
const fn = () => {};

常量命名

// ✅ 全大写 + 下划线
const MAX_VIDEO_DURATION = 7200;
const API_BASE_URL = 'https://api.example.com';

6. 导入顺序

// 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. 同步上游仓库

在开始新工作前,先同步最新的代码:

# 获取上游更新
git fetch upstream

# 切换到主分支
git checkout main

# 合并上游更新
git merge upstream/main

# 推送到你的 Fork
git push origin main

2. 创建功能分支

# 从 main 创建新分支
git checkout -b feat/your-feature-name

# 确认当前分支
git branch

3. 进行开发

在开发过程中:

  • 频繁提交小的、原子性的改动
  • 编写清晰的提交信息
  • 定期运行 npm run lint 检查代码

4. 提交前检查

必须通过的检查:

# 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. 推送分支

# 推送到你的 Fork
git push origin feat/your-feature-name

📝 提交规范

Conventional Commits

我们使用 Conventional Commits 规范:

<type>(<scope>): <subject>

<body>

<footer>

Type 类型:

  • feat:新功能
  • fix:错误修复
  • docs:文档变更
  • style:代码格式(不影响代码运行)
  • refactor:重构
  • perf:性能优化
  • test:测试相关
  • chore:构建过程或辅助工具的变动

示例:

# 简单提交
git commit -m "feat: 添加视频播放列表功能"

# 详细提交
git commit -m "feat(player): 添加倍速播放功能

- 支持 0.5x 到 2x 的播放速度
- 添加速度选择器 UI
- 保存用户的速度偏好

Closes #123"

提交信息最佳实践:

  • ✅ 使用中文或英文(保持一致)
  • ✅ 使用祈使句("添加功能" 而不是 "添加了功能")
  • ✅ 第一行不超过 50 个字符
  • ✅ 正文每行不超过 72 个字符
  • ✅ 说明 "做了什么" 和 "为什么",而不仅是 "怎么做"

🔍 Pull Request 指南

创建 PR

  1. 推送分支到你的 Fork
git push origin feat/your-feature-name
  1. 在 GitHub 上创建 PR
  • 访问你的 Fork 页面
  • 点击 "Compare & pull request"
  • 选择目标分支:KuekHaoYang/KVideo:main

PR 描述模板

## 📝 变更说明

简要描述这个 PR 做了什么。

## 🎯 相关 Issue

Closes #123
Fixes #456

## 📸 截图(如果是 UI 变更)

[如果有 UI 变更,添加截图或 GIF]

## ✅ 检查清单

- [ ] 代码已通过 `npm run lint`
- [ ] 所有文件都在 150 行以内
- [ ] 构建成功(`npm run build`- [ ] 已在本地测试所有变更
- [ ] 遵循 Liquid Glass 设计系统
- [ ] 提交信息符合规范
- [ ] 已更新相关文档

## 🧪 测试步骤

1. 第一步
2. 第二步
3. 预期结果

## 📌 额外说明

[任何其他需要 reviewer 知道的信息]

PR 审查流程

  1. 自动检查:GitHub Actions 会自动运行检查
  2. 代码审查:维护者会审查你的代码
  3. 修改请求:如果需要修改,会留下评论
  4. 批准和合并:审查通过后会被合并

回应审查意见

# 进行修改后
git add .
git commit -m "refactor: 根据审查意见调整代码"
git push origin feat/your-feature-name

PR 会自动更新。

🎨 设计系统规范

Liquid Glass 原则

在编写 UI 代码时,必须遵循 Liquid Glass 设计系统:

1. 圆角规范

Important

只使用两种圆角:rounded-2xlrounded-full

// ✅ 正确
<div className="rounded-2xl">  {/* 容器、卡片、按钮、输入框 */}
<div className="rounded-full"> {/* 头像、徽章、药丸形状 */}

// ❌ 错误
<div className="rounded-lg">
<div className="rounded-xl">
<div className="rounded-md">

2. 玻璃效果

// ✅ 使用 glass 类或 backdrop-filter
<div className="glass">
  {/* 内容 */}
</div>

// 或自定义玻璃效果
<div className="
  backdrop-blur-xl 
  backdrop-saturate-180 
  backdrop-brightness-110
  bg-white/10
  border border-white/20
">

3. 动画过渡

// ✅ 使用标准过渡曲线
<button className="
  transition-all 
  duration-300 
  ease-out
  hover:scale-105
">

4. 颜色系统

// ✅ 使用 CSS 变量
<div className="bg-glass text-glass-text border-glass-border">

// 或 Tailwind 的语义化颜色
<div className="bg-primary text-primary-foreground">

组件复用

优先复用 components/ui/ 下的基础组件:

// ✅ 好:复用基础组件
import { Button } from '@/components/ui/Button';
import { Modal } from '@/components/ui/Modal';

export function Feature() {
  return (
    <Modal>
      <Button variant="primary">确定</Button>
    </Modal>
  );
}

// ❌ 不好:重新实现基础组件
export function Feature() {
  return (
    <div className="modal">
      <button className="btn">确定</button>
    </div>
  );
}

🧪 测试要求

手动测试

在提交 PR 前,请手动测试以下内容:

功能测试

  • 新功能按预期工作
  • 没有破坏现有功能
  • 边界情况处理正确

浏览器测试

在以下浏览器中测试:

  • Chrome/Edge(最新版)
  • Firefox(最新版)
  • Safari(最新版)

响应式测试

在以下设备尺寸测试:

  • 移动端(375px - 428px)
  • 平板端(768px - 1024px)
  • 桌面端(1280px+)

无障碍测试

  • 键盘导航正常工作
  • 焦点状态清晰可见
  • 屏幕阅读器友好

代码检查

# 运行 ESLint
npm run lint

# 检查文件长度
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 "行"}'

❓ 常见问题

Q1: 我应该从哪里开始?

A: 查看标记为 good first issue 的 Issues,这些通常比较简单,适合新手。

Q2: 如何让文件保持在 150 行以内?

A: 参考 文件长度限制 部分的重构策略。关键是:

  • 提取组件
  • 提取 Hook
  • 提取工具函数
  • 模块化

注:系统文件(如 README.md、CONTRIBUTING.md 等文档)不受此限制。

  • 提取组件
  • 提取 Hook
  • 提取工具函数
  • 模块化

Q3: 我的 PR 多久会被审查?

A: 通常在 1-3 个工作日内。如果超过一周没有回应,可以在 PR 中添加评论提醒。

Q4: 可以同时提交多个 PR 吗?

A: 可以,但建议每个 PR 专注于一个功能或修复。避免在一个 PR 中做太多不相关的改动。

Q5: 如何解决合并冲突?

# 1. 同步上游
git fetch upstream
git checkout main
git merge upstream/main

# 2. 切换到功能分支并 rebase
git checkout feat/your-feature
git rebase main

# 3. 解决冲突后
git add .
git rebase --continue

# 4. 强制推送(因为 rebase 改变了历史)
git push origin feat/your-feature --force

Q6: 我的提交信息写错了怎么办?

# 修改最后一次提交
git commit --amend -m "新的提交信息"

# 如果已经推送了
git push origin feat/your-feature --force

Q7: 如何测试我的改动?

  1. 启动开发服务器:npm run dev
  2. 在浏览器中手动测试功能
  3. 测试不同的设备尺寸
  4. 运行 npm run build 确保生产构建成功

Q8: Liquid Glass 设计系统在哪里定义?

app/styles/glass.css 文件中。所有组件都应该基于这个设计系统。

Q9: 我需要更新文档吗?

如果你的 PR 包含以下内容,请更新相应文档:

  • 新功能:更新 README.md
  • API 变化:更新相关注释和文档
  • 配置变化:更新配置说明

Q10: 如何报告安全漏洞?

请查看 SECURITY.md 了解安全漏洞报告流程。不要在公开 Issue 中讨论安全问题。

📞 需要帮助?

如果你有任何问题:

  1. 查看文档:README.md 和本指南
  2. 搜索 Issues:可能已经有人问过相同的问题
  3. 提出问题:在 Discussions 或 Issues 中提问
  4. 联系维护者@KuekHaoYang

🎉 感谢你的贡献!

感谢你花时间阅读本指南,并为 KVideo 做出贡献。每一个贡献,无论大小,都让这个项目变得更好。

我们期待看到你的 Pull Request!


让我们一起打造更好的 KVideo!